@jspsych/plugin-video-keyboard-response 1.0.0 → 1.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/dist/index.browser.js +65 -10
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +1 -1
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +65 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +65 -10
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.spec.ts +77 -0
- package/src/index.ts +85 -10
package/dist/index.browser.js
CHANGED
|
@@ -104,6 +104,13 @@ var jsPsychVideoKeyboardResponse = (function (jspsych) {
|
|
|
104
104
|
this.jsPsych = jsPsych;
|
|
105
105
|
}
|
|
106
106
|
trial(display_element, trial) {
|
|
107
|
+
// catch mistake where stimuli are not an array
|
|
108
|
+
if (!Array.isArray(trial.stimulus)) {
|
|
109
|
+
throw new Error(`
|
|
110
|
+
The stimulus property for the video-keyboard-response plugin must be an array
|
|
111
|
+
of files. See https://www.jspsych.org/latest/plugins/video-keyboard-response/#parameters
|
|
112
|
+
`);
|
|
113
|
+
}
|
|
107
114
|
// setup stimulus
|
|
108
115
|
var video_html = "<div>";
|
|
109
116
|
video_html += '<video id="jspsych-video-keyboard-response-stimulus"';
|
|
@@ -173,7 +180,7 @@ var jsPsychVideoKeyboardResponse = (function (jspsych) {
|
|
|
173
180
|
// before showing and playing, so that the video doesn't automatically show the first frame
|
|
174
181
|
if (trial.start !== null) {
|
|
175
182
|
video_element.pause();
|
|
176
|
-
video_element.onseeked =
|
|
183
|
+
video_element.onseeked = () => {
|
|
177
184
|
video_element.style.visibility = "visible";
|
|
178
185
|
video_element.muted = false;
|
|
179
186
|
if (trial.autoplay) {
|
|
@@ -182,11 +189,11 @@ var jsPsychVideoKeyboardResponse = (function (jspsych) {
|
|
|
182
189
|
else {
|
|
183
190
|
video_element.pause();
|
|
184
191
|
}
|
|
185
|
-
video_element.onseeked =
|
|
192
|
+
video_element.onseeked = () => { };
|
|
186
193
|
};
|
|
187
|
-
video_element.onplaying =
|
|
194
|
+
video_element.onplaying = () => {
|
|
188
195
|
video_element.currentTime = trial.start;
|
|
189
|
-
video_element.onplaying =
|
|
196
|
+
video_element.onplaying = () => { };
|
|
190
197
|
};
|
|
191
198
|
// fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,
|
|
192
199
|
// change current time, then show/unmute
|
|
@@ -195,9 +202,18 @@ var jsPsychVideoKeyboardResponse = (function (jspsych) {
|
|
|
195
202
|
}
|
|
196
203
|
let stopped = false;
|
|
197
204
|
if (trial.stop !== null) {
|
|
198
|
-
video_element.addEventListener("timeupdate",
|
|
205
|
+
video_element.addEventListener("timeupdate", (e) => {
|
|
199
206
|
var currenttime = video_element.currentTime;
|
|
200
207
|
if (currenttime >= trial.stop) {
|
|
208
|
+
if (!trial.response_allowed_while_playing) {
|
|
209
|
+
this.jsPsych.pluginAPI.getKeyboardResponse({
|
|
210
|
+
callback_function: after_response,
|
|
211
|
+
valid_responses: trial.choices,
|
|
212
|
+
rt_method: "performance",
|
|
213
|
+
persist: false,
|
|
214
|
+
allow_held_key: false,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
201
217
|
video_element.pause();
|
|
202
218
|
if (trial.trial_ends_after_video && !stopped) {
|
|
203
219
|
// this is to prevent end_trial from being called twice, because the timeupdate event
|
|
@@ -224,7 +240,7 @@ var jsPsychVideoKeyboardResponse = (function (jspsych) {
|
|
|
224
240
|
display_element
|
|
225
241
|
.querySelector("#jspsych-video-keyboard-response-stimulus")
|
|
226
242
|
.pause();
|
|
227
|
-
display_element.querySelector("#jspsych-video-keyboard-response-stimulus").onended =
|
|
243
|
+
display_element.querySelector("#jspsych-video-keyboard-response-stimulus").onended = () => { };
|
|
228
244
|
// gather the data to store for the trial
|
|
229
245
|
var trial_data = {
|
|
230
246
|
rt: response.rt,
|
|
@@ -237,7 +253,7 @@ var jsPsychVideoKeyboardResponse = (function (jspsych) {
|
|
|
237
253
|
this.jsPsych.finishTrial(trial_data);
|
|
238
254
|
};
|
|
239
255
|
// function to handle responses by the subject
|
|
240
|
-
var after_response =
|
|
256
|
+
var after_response = (info) => {
|
|
241
257
|
// after a valid response, the stimulus will have the CSS class 'responded'
|
|
242
258
|
// which can be used to provide visual feedback that a response was recorded
|
|
243
259
|
display_element.querySelector("#jspsych-video-keyboard-response-stimulus").className +=
|
|
@@ -262,10 +278,49 @@ var jsPsychVideoKeyboardResponse = (function (jspsych) {
|
|
|
262
278
|
}
|
|
263
279
|
// end trial if time limit is set
|
|
264
280
|
if (trial.trial_duration !== null) {
|
|
265
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
266
|
-
|
|
267
|
-
|
|
281
|
+
this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
285
|
+
if (simulation_mode == "data-only") {
|
|
286
|
+
load_callback();
|
|
287
|
+
this.simulate_data_only(trial, simulation_options);
|
|
288
|
+
}
|
|
289
|
+
if (simulation_mode == "visual") {
|
|
290
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
simulate_data_only(trial, simulation_options) {
|
|
294
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
295
|
+
this.jsPsych.finishTrial(data);
|
|
296
|
+
}
|
|
297
|
+
simulate_visual(trial, simulation_options, load_callback) {
|
|
298
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
299
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
300
|
+
this.trial(display_element, trial);
|
|
301
|
+
load_callback();
|
|
302
|
+
const video_element = display_element.querySelector("#jspsych-video-button-response-stimulus");
|
|
303
|
+
const respond = () => {
|
|
304
|
+
if (data.rt !== null) {
|
|
305
|
+
this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
if (!trial.response_allowed_while_playing) {
|
|
309
|
+
video_element.addEventListener("ended", respond);
|
|
268
310
|
}
|
|
311
|
+
else {
|
|
312
|
+
respond();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
create_simulation_data(trial, simulation_options) {
|
|
316
|
+
const default_data = {
|
|
317
|
+
stimulus: trial.stimulus,
|
|
318
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
319
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
320
|
+
};
|
|
321
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
322
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
323
|
+
return data;
|
|
269
324
|
}
|
|
270
325
|
}
|
|
271
326
|
VideoKeyboardResponsePlugin.info = info;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"video-keyboard-response\",\n parameters: {\n /** Array of the video file(s) to play. Video can be provided in multiple file formats for better cross-browser support. */\n stimulus: {\n type: ParameterType.VIDEO,\n pretty_name: \"Video\",\n default: undefined,\n array: true,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The width of the video in pixels. */\n width: {\n type: ParameterType.INT,\n pretty_name: \"Width\",\n default: \"\",\n },\n /** The height of the video display in pixels. */\n height: {\n type: ParameterType.INT,\n pretty_name: \"Height\",\n default: \"\",\n },\n /** If true, the video will begin playing as soon as it has loaded. */\n autoplay: {\n type: ParameterType.BOOL,\n pretty_name: \"Autoplay\",\n default: true,\n },\n /** If true, the subject will be able to pause the video or move the playback to any point in the video. */\n controls: {\n type: ParameterType.BOOL,\n pretty_name: \"Controls\",\n default: false,\n },\n /** Time to start the clip. If null (default), video will start at the beginning of the file. */\n start: {\n type: ParameterType.FLOAT,\n pretty_name: \"Start\",\n default: null,\n },\n /** Time to stop the clip. If null (default), video will stop at the end of the file. */\n stop: {\n type: ParameterType.FLOAT,\n pretty_name: \"Stop\",\n default: null,\n },\n /** The playback rate of the video. 1 is normal, <1 is slower, >1 is faster. */\n rate: {\n type: ParameterType.FLOAT,\n pretty_name: \"Rate\",\n default: 1,\n },\n /** If true, the trial will end immediately after the video finishes playing. */\n trial_ends_after_video: {\n type: ParameterType.BOOL,\n pretty_name: \"End trial after video finishes\",\n default: false,\n },\n /** How long to show trial before it ends. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when subject makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then responses are allowed while the video is playing. If false, then the video must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **video-keyboard-response**\n *\n * jsPsych plugin for playing a video file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-video-keyboard-response/ video-keyboard-response plugin documentation on jspsych.org}\n */\nclass VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // setup stimulus\n var video_html = \"<div>\";\n video_html += '<video id=\"jspsych-video-keyboard-response-stimulus\"';\n\n if (trial.width) {\n video_html += ' width=\"' + trial.width + '\"';\n }\n if (trial.height) {\n video_html += ' height=\"' + trial.height + '\"';\n }\n if (trial.autoplay && trial.start == null) {\n // if autoplay is true and the start time is specified, then the video will start automatically\n // via the play() method, rather than the autoplay attribute, to prevent showing the first frame\n video_html += \" autoplay \";\n }\n if (trial.controls) {\n video_html += \" controls \";\n }\n if (trial.start !== null) {\n // hide video element when page loads if the start time is specified,\n // to prevent the video element from showing the first frame\n video_html += ' style=\"visibility: hidden;\"';\n }\n video_html += \">\";\n\n var video_preload_blob = this.jsPsych.pluginAPI.getVideoBuffer(trial.stimulus[0]);\n if (!video_preload_blob) {\n for (var i = 0; i < trial.stimulus.length; i++) {\n var file_name = trial.stimulus[i];\n if (file_name.indexOf(\"?\") > -1) {\n file_name = file_name.substring(0, file_name.indexOf(\"?\"));\n }\n var type = file_name.substr(file_name.lastIndexOf(\".\") + 1);\n type = type.toLowerCase();\n if (type == \"mov\") {\n console.warn(\n \"Warning: video-keyboard-response plugin does not reliably support .mov files.\"\n );\n }\n video_html += '<source src=\"' + file_name + '\" type=\"video/' + type + '\">';\n }\n }\n video_html += \"</video>\";\n video_html += \"</div>\";\n\n // add prompt if there is one\n if (trial.prompt !== null) {\n video_html += trial.prompt;\n }\n\n display_element.innerHTML = video_html;\n\n var video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n );\n\n if (video_preload_blob) {\n video_element.src = video_preload_blob;\n }\n\n video_element.onended = () => {\n if (trial.trial_ends_after_video) {\n end_trial();\n }\n if (trial.response_allowed_while_playing == false && !trial.trial_ends_after_video) {\n // start keyboard listener\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n video_element.playbackRate = trial.rate;\n\n // if video start time is specified, hide the video and set the starting time\n // before showing and playing, so that the video doesn't automatically show the first frame\n if (trial.start !== null) {\n video_element.pause();\n video_element.onseeked = function () {\n video_element.style.visibility = \"visible\";\n video_element.muted = false;\n if (trial.autoplay) {\n video_element.play();\n } else {\n video_element.pause();\n }\n video_element.onseeked = function () {};\n };\n video_element.onplaying = function () {\n video_element.currentTime = trial.start;\n video_element.onplaying = function () {};\n };\n // fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,\n // change current time, then show/unmute\n video_element.muted = true;\n video_element.play();\n }\n\n let stopped = false;\n if (trial.stop !== null) {\n video_element.addEventListener(\"timeupdate\", function (e) {\n var currenttime = video_element.currentTime;\n if (currenttime >= trial.stop) {\n video_element.pause();\n if (trial.trial_ends_after_video && !stopped) {\n // this is to prevent end_trial from being called twice, because the timeupdate event\n // can fire in quick succession\n stopped = true;\n end_trial();\n }\n }\n });\n }\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // stop the video file if it is playing\n // remove end event listeners if they exist\n display_element\n .querySelector<HTMLVideoElement>(\"#jspsych-video-keyboard-response-stimulus\")\n .pause();\n display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n ).onended = function () {};\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n };\n\n // function to handle responses by the subject\n var after_response = function (info) {\n // after a valid response, the stimulus will have the CSS class 'responded'\n // which can be used to provide visual feedback that a response was recorded\n display_element.querySelector(\"#jspsych-video-keyboard-response-stimulus\").className +=\n \" responded\";\n\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n };\n\n // start the response listener\n if (trial.choices != \"NO_KEYS\" && trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n }\n}\n\nexport default VideoKeyboardResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"video-keyboard-response\",\n parameters: {\n /** Array of the video file(s) to play. Video can be provided in multiple file formats for better cross-browser support. */\n stimulus: {\n type: ParameterType.VIDEO,\n pretty_name: \"Video\",\n default: undefined,\n array: true,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The width of the video in pixels. */\n width: {\n type: ParameterType.INT,\n pretty_name: \"Width\",\n default: \"\",\n },\n /** The height of the video display in pixels. */\n height: {\n type: ParameterType.INT,\n pretty_name: \"Height\",\n default: \"\",\n },\n /** If true, the video will begin playing as soon as it has loaded. */\n autoplay: {\n type: ParameterType.BOOL,\n pretty_name: \"Autoplay\",\n default: true,\n },\n /** If true, the subject will be able to pause the video or move the playback to any point in the video. */\n controls: {\n type: ParameterType.BOOL,\n pretty_name: \"Controls\",\n default: false,\n },\n /** Time to start the clip. If null (default), video will start at the beginning of the file. */\n start: {\n type: ParameterType.FLOAT,\n pretty_name: \"Start\",\n default: null,\n },\n /** Time to stop the clip. If null (default), video will stop at the end of the file. */\n stop: {\n type: ParameterType.FLOAT,\n pretty_name: \"Stop\",\n default: null,\n },\n /** The playback rate of the video. 1 is normal, <1 is slower, >1 is faster. */\n rate: {\n type: ParameterType.FLOAT,\n pretty_name: \"Rate\",\n default: 1,\n },\n /** If true, the trial will end immediately after the video finishes playing. */\n trial_ends_after_video: {\n type: ParameterType.BOOL,\n pretty_name: \"End trial after video finishes\",\n default: false,\n },\n /** How long to show trial before it ends. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when subject makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then responses are allowed while the video is playing. If false, then the video must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **video-keyboard-response**\n *\n * jsPsych plugin for playing a video file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-video-keyboard-response/ video-keyboard-response plugin documentation on jspsych.org}\n */\nclass VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // catch mistake where stimuli are not an array\n if (!Array.isArray(trial.stimulus)) {\n throw new Error(`\n The stimulus property for the video-keyboard-response plugin must be an array\n of files. See https://www.jspsych.org/latest/plugins/video-keyboard-response/#parameters\n `);\n }\n\n // setup stimulus\n var video_html = \"<div>\";\n video_html += '<video id=\"jspsych-video-keyboard-response-stimulus\"';\n\n if (trial.width) {\n video_html += ' width=\"' + trial.width + '\"';\n }\n if (trial.height) {\n video_html += ' height=\"' + trial.height + '\"';\n }\n if (trial.autoplay && trial.start == null) {\n // if autoplay is true and the start time is specified, then the video will start automatically\n // via the play() method, rather than the autoplay attribute, to prevent showing the first frame\n video_html += \" autoplay \";\n }\n if (trial.controls) {\n video_html += \" controls \";\n }\n if (trial.start !== null) {\n // hide video element when page loads if the start time is specified,\n // to prevent the video element from showing the first frame\n video_html += ' style=\"visibility: hidden;\"';\n }\n video_html += \">\";\n\n var video_preload_blob = this.jsPsych.pluginAPI.getVideoBuffer(trial.stimulus[0]);\n if (!video_preload_blob) {\n for (var i = 0; i < trial.stimulus.length; i++) {\n var file_name = trial.stimulus[i];\n if (file_name.indexOf(\"?\") > -1) {\n file_name = file_name.substring(0, file_name.indexOf(\"?\"));\n }\n var type = file_name.substr(file_name.lastIndexOf(\".\") + 1);\n type = type.toLowerCase();\n if (type == \"mov\") {\n console.warn(\n \"Warning: video-keyboard-response plugin does not reliably support .mov files.\"\n );\n }\n video_html += '<source src=\"' + file_name + '\" type=\"video/' + type + '\">';\n }\n }\n video_html += \"</video>\";\n video_html += \"</div>\";\n\n // add prompt if there is one\n if (trial.prompt !== null) {\n video_html += trial.prompt;\n }\n\n display_element.innerHTML = video_html;\n\n var video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n );\n\n if (video_preload_blob) {\n video_element.src = video_preload_blob;\n }\n\n video_element.onended = () => {\n if (trial.trial_ends_after_video) {\n end_trial();\n }\n if (trial.response_allowed_while_playing == false && !trial.trial_ends_after_video) {\n // start keyboard listener\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n video_element.playbackRate = trial.rate;\n\n // if video start time is specified, hide the video and set the starting time\n // before showing and playing, so that the video doesn't automatically show the first frame\n if (trial.start !== null) {\n video_element.pause();\n video_element.onseeked = () => {\n video_element.style.visibility = \"visible\";\n video_element.muted = false;\n if (trial.autoplay) {\n video_element.play();\n } else {\n video_element.pause();\n }\n video_element.onseeked = () => {};\n };\n video_element.onplaying = () => {\n video_element.currentTime = trial.start;\n video_element.onplaying = () => {};\n };\n // fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,\n // change current time, then show/unmute\n video_element.muted = true;\n video_element.play();\n }\n\n let stopped = false;\n if (trial.stop !== null) {\n video_element.addEventListener(\"timeupdate\", (e) => {\n var currenttime = video_element.currentTime;\n if (currenttime >= trial.stop) {\n if (!trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n video_element.pause();\n if (trial.trial_ends_after_video && !stopped) {\n // this is to prevent end_trial from being called twice, because the timeupdate event\n // can fire in quick succession\n stopped = true;\n end_trial();\n }\n }\n });\n }\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // stop the video file if it is playing\n // remove end event listeners if they exist\n display_element\n .querySelector<HTMLVideoElement>(\"#jspsych-video-keyboard-response-stimulus\")\n .pause();\n display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n ).onended = () => {};\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n };\n\n // function to handle responses by the subject\n var after_response = (info) => {\n // after a valid response, the stimulus will have the CSS class 'responded'\n // which can be used to provide visual feedback that a response was recorded\n display_element.querySelector(\"#jspsych-video-keyboard-response-stimulus\").className +=\n \" responded\";\n\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n };\n\n // start the response listener\n if (trial.choices != \"NO_KEYS\" && trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration);\n }\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-button-response-stimulus\"\n );\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.pressKey(data.response, data.rt);\n }\n };\n\n if (!trial.response_allowed_while_playing) {\n video_element.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.pluginAPI.getValidKey(trial.choices),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n}\n\nexport default VideoKeyboardResponsePlugin;\n"],"names":["ParameterType"],"mappings":";;;EAEA,MAAM,IAAI,GAAU;MAClB,IAAI,EAAE,yBAAyB;MAC/B,UAAU,EAAE;;UAEV,QAAQ,EAAE;cACR,IAAI,EAAEA,qBAAa,CAAC,KAAK;cACzB,WAAW,EAAE,OAAO;cACpB,OAAO,EAAE,SAAS;cAClB,KAAK,EAAE,IAAI;WACZ;;UAED,OAAO,EAAE;cACP,IAAI,EAAEA,qBAAa,CAAC,IAAI;cACxB,WAAW,EAAE,SAAS;cACtB,OAAO,EAAE,UAAU;WACpB;;UAED,MAAM,EAAE;cACN,IAAI,EAAEA,qBAAa,CAAC,WAAW;cAC/B,WAAW,EAAE,QAAQ;cACrB,OAAO,EAAE,IAAI;WACd;;UAED,KAAK,EAAE;cACL,IAAI,EAAEA,qBAAa,CAAC,GAAG;cACvB,WAAW,EAAE,OAAO;cACpB,OAAO,EAAE,EAAE;WACZ;;UAED,MAAM,EAAE;cACN,IAAI,EAAEA,qBAAa,CAAC,GAAG;cACvB,WAAW,EAAE,QAAQ;cACrB,OAAO,EAAE,EAAE;WACZ;;UAED,QAAQ,EAAE;cACR,IAAI,EAAEA,qBAAa,CAAC,IAAI;cACxB,WAAW,EAAE,UAAU;cACvB,OAAO,EAAE,IAAI;WACd;;UAED,QAAQ,EAAE;cACR,IAAI,EAAEA,qBAAa,CAAC,IAAI;cACxB,WAAW,EAAE,UAAU;cACvB,OAAO,EAAE,KAAK;WACf;;UAED,KAAK,EAAE;cACL,IAAI,EAAEA,qBAAa,CAAC,KAAK;cACzB,WAAW,EAAE,OAAO;cACpB,OAAO,EAAE,IAAI;WACd;;UAED,IAAI,EAAE;cACJ,IAAI,EAAEA,qBAAa,CAAC,KAAK;cACzB,WAAW,EAAE,MAAM;cACnB,OAAO,EAAE,IAAI;WACd;;UAED,IAAI,EAAE;cACJ,IAAI,EAAEA,qBAAa,CAAC,KAAK;cACzB,WAAW,EAAE,MAAM;cACnB,OAAO,EAAE,CAAC;WACX;;UAED,sBAAsB,EAAE;cACtB,IAAI,EAAEA,qBAAa,CAAC,IAAI;cACxB,WAAW,EAAE,gCAAgC;cAC7C,OAAO,EAAE,KAAK;WACf;;UAED,cAAc,EAAE;cACd,IAAI,EAAEA,qBAAa,CAAC,GAAG;cACvB,WAAW,EAAE,gBAAgB;cAC7B,OAAO,EAAE,IAAI;WACd;;UAED,mBAAmB,EAAE;cACnB,IAAI,EAAEA,qBAAa,CAAC,IAAI;cACxB,WAAW,EAAE,qBAAqB;cAClC,OAAO,EAAE,IAAI;WACd;;UAED,8BAA8B,EAAE;cAC9B,IAAI,EAAEA,qBAAa,CAAC,IAAI;cACxB,WAAW,EAAE,gCAAgC;cAC7C,OAAO,EAAE,IAAI;WACd;OACF;GACF,CAAC;EAIF;;;;;;;;EAQA,MAAM,2BAA2B;MAG/B,YAAoB,OAAgB;UAAhB,YAAO,GAAP,OAAO,CAAS;OAAI;MAExC,KAAK,CAAC,eAA4B,EAAE,KAAsB;;UAExD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE;cAClC,MAAM,IAAI,KAAK,CAAC;;;OAGf,CAAC,CAAC;WACJ;;UAGD,IAAI,UAAU,GAAG,OAAO,CAAC;UACzB,UAAU,IAAI,sDAAsD,CAAC;UAErE,IAAI,KAAK,CAAC,KAAK,EAAE;cACf,UAAU,IAAI,UAAU,GAAG,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC;WAC9C;UACD,IAAI,KAAK,CAAC,MAAM,EAAE;cAChB,UAAU,IAAI,WAAW,GAAG,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC;WAChD;UACD,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI,EAAE;;;cAGzC,UAAU,IAAI,YAAY,CAAC;WAC5B;UACD,IAAI,KAAK,CAAC,QAAQ,EAAE;cAClB,UAAU,IAAI,YAAY,CAAC;WAC5B;UACD,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,EAAE;;;cAGxB,UAAU,IAAI,8BAA8B,CAAC;WAC9C;UACD,UAAU,IAAI,GAAG,CAAC;UAElB,IAAI,kBAAkB,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;UAClF,IAAI,CAAC,kBAAkB,EAAE;cACvB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;kBAC9C,IAAI,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;kBAClC,IAAI,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE;sBAC/B,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;mBAC5D;kBACD,IAAI,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;kBAC5D,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;kBAC1B,IAAI,IAAI,IAAI,KAAK,EAAE;sBACjB,OAAO,CAAC,IAAI,CACV,+EAA+E,CAChF,CAAC;mBACH;kBACD,UAAU,IAAI,eAAe,GAAG,SAAS,GAAG,gBAAgB,GAAG,IAAI,GAAG,IAAI,CAAC;eAC5E;WACF;UACD,UAAU,IAAI,UAAU,CAAC;UACzB,UAAU,IAAI,QAAQ,CAAC;;UAGvB,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,EAAE;cACzB,UAAU,IAAI,KAAK,CAAC,MAAM,CAAC;WAC5B;UAED,eAAe,CAAC,SAAS,GAAG,UAAU,CAAC;UAEvC,IAAI,aAAa,GAAG,eAAe,CAAC,aAAa,CAC/C,2CAA2C,CAC5C,CAAC;UAEF,IAAI,kBAAkB,EAAE;cACtB,aAAa,CAAC,GAAG,GAAG,kBAAkB,CAAC;WACxC;UAED,aAAa,CAAC,OAAO,GAAG;cACtB,IAAI,KAAK,CAAC,sBAAsB,EAAE;kBAChC,SAAS,EAAE,CAAC;eACb;cACD,IAAI,KAAK,CAAC,8BAA8B,IAAI,KAAK,IAAI,CAAC,KAAK,CAAC,sBAAsB,EAAE;;kBAE3D,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,mBAAmB,CAAC;sBAChE,iBAAiB,EAAE,cAAc;sBACjC,eAAe,EAAE,KAAK,CAAC,OAAO;sBAC9B,SAAS,EAAE,aAAa;sBACxB,OAAO,EAAE,KAAK;sBACd,cAAc,EAAE,KAAK;mBACtB,EAAE;eACJ;WACF,CAAC;UAEF,aAAa,CAAC,YAAY,GAAG,KAAK,CAAC,IAAI,CAAC;;;UAIxC,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,EAAE;cACxB,aAAa,CAAC,KAAK,EAAE,CAAC;cACtB,aAAa,CAAC,QAAQ,GAAG;kBACvB,aAAa,CAAC,KAAK,CAAC,UAAU,GAAG,SAAS,CAAC;kBAC3C,aAAa,CAAC,KAAK,GAAG,KAAK,CAAC;kBAC5B,IAAI,KAAK,CAAC,QAAQ,EAAE;sBAClB,aAAa,CAAC,IAAI,EAAE,CAAC;mBACtB;uBAAM;sBACL,aAAa,CAAC,KAAK,EAAE,CAAC;mBACvB;kBACD,aAAa,CAAC,QAAQ,GAAG,SAAQ,CAAC;eACnC,CAAC;cACF,aAAa,CAAC,SAAS,GAAG;kBACxB,aAAa,CAAC,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC;kBACxC,aAAa,CAAC,SAAS,GAAG,SAAQ,CAAC;eACpC,CAAC;;;cAGF,aAAa,CAAC,KAAK,GAAG,IAAI,CAAC;cAC3B,aAAa,CAAC,IAAI,EAAE,CAAC;WACtB;UAED,IAAI,OAAO,GAAG,KAAK,CAAC;UACpB,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,EAAE;cACvB,aAAa,CAAC,gBAAgB,CAAC,YAAY,EAAE,CAAC,CAAC;kBAC7C,IAAI,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC;kBAC5C,IAAI,WAAW,IAAI,KAAK,CAAC,IAAI,EAAE;sBAC7B,IAAI,CAAC,KAAK,CAAC,8BAA8B,EAAE;0BAClB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,mBAAmB,CAAC;8BAChE,iBAAiB,EAAE,cAAc;8BACjC,eAAe,EAAE,KAAK,CAAC,OAAO;8BAC9B,SAAS,EAAE,aAAa;8BACxB,OAAO,EAAE,KAAK;8BACd,cAAc,EAAE,KAAK;2BACtB,EAAE;uBACJ;sBACD,aAAa,CAAC,KAAK,EAAE,CAAC;sBACtB,IAAI,KAAK,CAAC,sBAAsB,IAAI,CAAC,OAAO,EAAE;;;0BAG5C,OAAO,GAAG,IAAI,CAAC;0BACf,SAAS,EAAE,CAAC;uBACb;mBACF;eACF,CAAC,CAAC;WACJ;;UAGD,IAAI,QAAQ,GAAG;cACb,EAAE,EAAE,IAAI;cACR,GAAG,EAAE,IAAI;WACV,CAAC;;UAGF,MAAM,SAAS,GAAG;;cAEhB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,gBAAgB,EAAE,CAAC;;cAG1C,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,0BAA0B,EAAE,CAAC;;;cAIpD,eAAe;mBACZ,aAAa,CAAmB,2CAA2C,CAAC;mBAC5E,KAAK,EAAE,CAAC;cACX,eAAe,CAAC,aAAa,CAC3B,2CAA2C,CAC5C,CAAC,OAAO,GAAG,SAAQ,CAAC;;cAGrB,IAAI,UAAU,GAAG;kBACf,EAAE,EAAE,QAAQ,CAAC,EAAE;kBACf,QAAQ,EAAE,KAAK,CAAC,QAAQ;kBACxB,QAAQ,EAAE,QAAQ,CAAC,GAAG;eACvB,CAAC;;cAGF,eAAe,CAAC,SAAS,GAAG,EAAE,CAAC;;cAG/B,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;WACtC,CAAC;;UAGF,IAAI,cAAc,GAAG,CAAC,IAAI;;;cAGxB,eAAe,CAAC,aAAa,CAAC,2CAA2C,CAAC,CAAC,SAAS;kBAClF,YAAY,CAAC;;cAGf,IAAI,QAAQ,CAAC,GAAG,IAAI,IAAI,EAAE;kBACxB,QAAQ,GAAG,IAAI,CAAC;eACjB;cAED,IAAI,KAAK,CAAC,mBAAmB,EAAE;kBAC7B,SAAS,EAAE,CAAC;eACb;WACF,CAAC;;UAGF,IAAI,KAAK,CAAC,OAAO,IAAI,SAAS,IAAI,KAAK,CAAC,8BAA8B,EAAE;cAC/C,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,mBAAmB,CAAC;kBAChE,iBAAiB,EAAE,cAAc;kBACjC,eAAe,EAAE,KAAK,CAAC,OAAO;kBAC9B,SAAS,EAAE,aAAa;kBACxB,OAAO,EAAE,KAAK;kBACd,cAAc,EAAE,KAAK;eACtB,EAAE;WACJ;;UAGD,IAAI,KAAK,CAAC,cAAc,KAAK,IAAI,EAAE;cACjC,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;WACpE;OACF;MAED,QAAQ,CACN,KAAsB,EACtB,eAAe,EACf,kBAAuB,EACvB,aAAyB;UAEzB,IAAI,eAAe,IAAI,WAAW,EAAE;cAClC,aAAa,EAAE,CAAC;cAChB,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;WACpD;UACD,IAAI,eAAe,IAAI,QAAQ,EAAE;cAC/B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,kBAAkB,EAAE,aAAa,CAAC,CAAC;WAChE;OACF;MAEO,kBAAkB,CAAC,KAAsB,EAAE,kBAAkB;UACnE,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;UAEpE,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;OAChC;MAEO,eAAe,CAAC,KAAsB,EAAE,kBAAkB,EAAE,aAAyB;UAC3F,MAAM,IAAI,GAAG,IAAI,CAAC,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAC;UAEpE,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;UAEzD,IAAI,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;UACnC,aAAa,EAAE,CAAC;UAEhB,MAAM,aAAa,GAAG,eAAe,CAAC,aAAa,CACjD,yCAAyC,CAC1C,CAAC;UAEF,MAAM,OAAO,GAAG;cACd,IAAI,IAAI,CAAC,EAAE,KAAK,IAAI,EAAE;kBACpB,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;eACzD;WACF,CAAC;UAEF,IAAI,CAAC,KAAK,CAAC,8BAA8B,EAAE;cACzC,aAAa,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;WAClD;eAAM;cACL,OAAO,EAAE,CAAC;WACX;OACF;MAEO,sBAAsB,CAAC,KAAsB,EAAE,kBAAkB;UACvE,MAAM,YAAY,GAAG;cACnB,QAAQ,EAAE,KAAK,CAAC,QAAQ;cACxB,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,gBAAgB,CAAC,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,GAAG,EAAE,IAAI,CAAC;cACvE,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC;WAC5D,CAAC;UAEF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,mBAAmB,CAAC,YAAY,EAAE,kBAAkB,CAAC,CAAC;UAE1F,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,+BAA+B,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;UAEpE,OAAO,IAAI,CAAC;OACb;;EA7QM,gCAAI,GAAG,IAAI;;;;;;;;"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var jsPsychVideoKeyboardResponse=function(e){"use strict";const t={name:"video-keyboard-response",parameters:{stimulus:{type:e.ParameterType.VIDEO,pretty_name:"Video",default:void 0,array:!0},choices:{type:e.ParameterType.KEYS,pretty_name:"Choices",default:"ALL_KEYS"},prompt:{type:e.ParameterType.HTML_STRING,pretty_name:"Prompt",default:null},width:{type:e.ParameterType.INT,pretty_name:"Width",default:""},height:{type:e.ParameterType.INT,pretty_name:"Height",default:""},autoplay:{type:e.ParameterType.BOOL,pretty_name:"Autoplay",default:!0},controls:{type:e.ParameterType.BOOL,pretty_name:"Controls",default:!1},start:{type:e.ParameterType.FLOAT,pretty_name:"Start",default:null},stop:{type:e.ParameterType.FLOAT,pretty_name:"Stop",default:null},rate:{type:e.ParameterType.FLOAT,pretty_name:"Rate",default:1},trial_ends_after_video:{type:e.ParameterType.BOOL,pretty_name:"End trial after video finishes",default:!1},trial_duration:{type:e.ParameterType.INT,pretty_name:"Trial duration",default:null},response_ends_trial:{type:e.ParameterType.BOOL,pretty_name:"Response ends trial",default:!0},response_allowed_while_playing:{type:e.ParameterType.BOOL,pretty_name:"Response allowed while playing",default:!0}}};class s{constructor(e){this.jsPsych=e}trial(e,t){var s="<div>";s+='<video id="jspsych-video-keyboard-response-stimulus"',t.width&&(s+=' width="'+t.width+'"'),t.height&&(s+=' height="'+t.height+'"'),t.autoplay&&null==t.start&&(s+=" autoplay "),t.controls&&(s+=" controls "),null!==t.start&&(s+=' style="visibility: hidden;"'),s+=">";var
|
|
1
|
+
var jsPsychVideoKeyboardResponse=function(e){"use strict";const t={name:"video-keyboard-response",parameters:{stimulus:{type:e.ParameterType.VIDEO,pretty_name:"Video",default:void 0,array:!0},choices:{type:e.ParameterType.KEYS,pretty_name:"Choices",default:"ALL_KEYS"},prompt:{type:e.ParameterType.HTML_STRING,pretty_name:"Prompt",default:null},width:{type:e.ParameterType.INT,pretty_name:"Width",default:""},height:{type:e.ParameterType.INT,pretty_name:"Height",default:""},autoplay:{type:e.ParameterType.BOOL,pretty_name:"Autoplay",default:!0},controls:{type:e.ParameterType.BOOL,pretty_name:"Controls",default:!1},start:{type:e.ParameterType.FLOAT,pretty_name:"Start",default:null},stop:{type:e.ParameterType.FLOAT,pretty_name:"Stop",default:null},rate:{type:e.ParameterType.FLOAT,pretty_name:"Rate",default:1},trial_ends_after_video:{type:e.ParameterType.BOOL,pretty_name:"End trial after video finishes",default:!1},trial_duration:{type:e.ParameterType.INT,pretty_name:"Trial duration",default:null},response_ends_trial:{type:e.ParameterType.BOOL,pretty_name:"Response ends trial",default:!0},response_allowed_while_playing:{type:e.ParameterType.BOOL,pretty_name:"Response allowed while playing",default:!0}}};class s{constructor(e){this.jsPsych=e}trial(e,t){if(!Array.isArray(t.stimulus))throw new Error("\n The stimulus property for the video-keyboard-response plugin must be an array\n of files. See https://www.jspsych.org/latest/plugins/video-keyboard-response/#parameters\n ");var s="<div>";s+='<video id="jspsych-video-keyboard-response-stimulus"',t.width&&(s+=' width="'+t.width+'"'),t.height&&(s+=' height="'+t.height+'"'),t.autoplay&&null==t.start&&(s+=" autoplay "),t.controls&&(s+=" controls "),null!==t.start&&(s+=' style="visibility: hidden;"'),s+=">";var a=this.jsPsych.pluginAPI.getVideoBuffer(t.stimulus[0]);if(!a)for(var r=0;r<t.stimulus.length;r++){var i=t.stimulus[r];i.indexOf("?")>-1&&(i=i.substring(0,i.indexOf("?")));var l=i.substr(i.lastIndexOf(".")+1);"mov"==(l=l.toLowerCase())&&console.warn("Warning: video-keyboard-response plugin does not reliably support .mov files."),s+='<source src="'+i+'" type="video/'+l+'">'}s+="</video>",s+="</div>",null!==t.prompt&&(s+=t.prompt),e.innerHTML=s;var n=e.querySelector("#jspsych-video-keyboard-response-stimulus");a&&(n.src=a),n.onended=()=>{t.trial_ends_after_video&&u(),0!=t.response_allowed_while_playing||t.trial_ends_after_video||this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:y,valid_responses:t.choices,rt_method:"performance",persist:!1,allow_held_key:!1})},n.playbackRate=t.rate,null!==t.start&&(n.pause(),n.onseeked=()=>{n.style.visibility="visible",n.muted=!1,t.autoplay?n.play():n.pause(),n.onseeked=()=>{}},n.onplaying=()=>{n.currentTime=t.start,n.onplaying=()=>{}},n.muted=!0,n.play());let o=!1;null!==t.stop&&n.addEventListener("timeupdate",(e=>{n.currentTime>=t.stop&&(t.response_allowed_while_playing||this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:y,valid_responses:t.choices,rt_method:"performance",persist:!1,allow_held_key:!1}),n.pause(),t.trial_ends_after_video&&!o&&(o=!0,u()))}));var p={rt:null,key:null};const u=()=>{this.jsPsych.pluginAPI.clearAllTimeouts(),this.jsPsych.pluginAPI.cancelAllKeyboardResponses(),e.querySelector("#jspsych-video-keyboard-response-stimulus").pause(),e.querySelector("#jspsych-video-keyboard-response-stimulus").onended=()=>{};var s={rt:p.rt,stimulus:t.stimulus,response:p.key};e.innerHTML="",this.jsPsych.finishTrial(s)};var y=s=>{e.querySelector("#jspsych-video-keyboard-response-stimulus").className+=" responded",null==p.key&&(p=s),t.response_ends_trial&&u()};"NO_KEYS"!=t.choices&&t.response_allowed_while_playing&&this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:y,valid_responses:t.choices,rt_method:"performance",persist:!1,allow_held_key:!1}),null!==t.trial_duration&&this.jsPsych.pluginAPI.setTimeout(u,t.trial_duration)}simulate(e,t,s,a){"data-only"==t&&(a(),this.simulate_data_only(e,s)),"visual"==t&&this.simulate_visual(e,s,a)}simulate_data_only(e,t){const s=this.create_simulation_data(e,t);this.jsPsych.finishTrial(s)}simulate_visual(e,t,s){const a=this.create_simulation_data(e,t),r=this.jsPsych.getDisplayElement();this.trial(r,e),s();const i=r.querySelector("#jspsych-video-button-response-stimulus"),l=()=>{null!==a.rt&&this.jsPsych.pluginAPI.pressKey(a.response,a.rt)};e.response_allowed_while_playing?l():i.addEventListener("ended",l)}create_simulation_data(e,t){const s={stimulus:e.stimulus,rt:this.jsPsych.randomization.sampleExGaussian(500,50,1/150,!0),response:this.jsPsych.pluginAPI.getValidKey(e.choices)},a=this.jsPsych.pluginAPI.mergeSimulationData(s,t);return this.jsPsych.pluginAPI.ensureSimulationDataConsistency(e,a),a}}return s.info=t,s}(jsPsychModule);
|
|
2
2
|
//# sourceMappingURL=index.browser.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.browser.min.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"video-keyboard-response\",\n parameters: {\n /** Array of the video file(s) to play. Video can be provided in multiple file formats for better cross-browser support. */\n stimulus: {\n type: ParameterType.VIDEO,\n pretty_name: \"Video\",\n default: undefined,\n array: true,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The width of the video in pixels. */\n width: {\n type: ParameterType.INT,\n pretty_name: \"Width\",\n default: \"\",\n },\n /** The height of the video display in pixels. */\n height: {\n type: ParameterType.INT,\n pretty_name: \"Height\",\n default: \"\",\n },\n /** If true, the video will begin playing as soon as it has loaded. */\n autoplay: {\n type: ParameterType.BOOL,\n pretty_name: \"Autoplay\",\n default: true,\n },\n /** If true, the subject will be able to pause the video or move the playback to any point in the video. */\n controls: {\n type: ParameterType.BOOL,\n pretty_name: \"Controls\",\n default: false,\n },\n /** Time to start the clip. If null (default), video will start at the beginning of the file. */\n start: {\n type: ParameterType.FLOAT,\n pretty_name: \"Start\",\n default: null,\n },\n /** Time to stop the clip. If null (default), video will stop at the end of the file. */\n stop: {\n type: ParameterType.FLOAT,\n pretty_name: \"Stop\",\n default: null,\n },\n /** The playback rate of the video. 1 is normal, <1 is slower, >1 is faster. */\n rate: {\n type: ParameterType.FLOAT,\n pretty_name: \"Rate\",\n default: 1,\n },\n /** If true, the trial will end immediately after the video finishes playing. */\n trial_ends_after_video: {\n type: ParameterType.BOOL,\n pretty_name: \"End trial after video finishes\",\n default: false,\n },\n /** How long to show trial before it ends. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when subject makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then responses are allowed while the video is playing. If false, then the video must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **video-keyboard-response**\n *\n * jsPsych plugin for playing a video file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-video-keyboard-response/ video-keyboard-response plugin documentation on jspsych.org}\n */\nclass VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // setup stimulus\n var video_html = \"<div>\";\n video_html += '<video id=\"jspsych-video-keyboard-response-stimulus\"';\n\n if (trial.width) {\n video_html += ' width=\"' + trial.width + '\"';\n }\n if (trial.height) {\n video_html += ' height=\"' + trial.height + '\"';\n }\n if (trial.autoplay && trial.start == null) {\n // if autoplay is true and the start time is specified, then the video will start automatically\n // via the play() method, rather than the autoplay attribute, to prevent showing the first frame\n video_html += \" autoplay \";\n }\n if (trial.controls) {\n video_html += \" controls \";\n }\n if (trial.start !== null) {\n // hide video element when page loads if the start time is specified,\n // to prevent the video element from showing the first frame\n video_html += ' style=\"visibility: hidden;\"';\n }\n video_html += \">\";\n\n var video_preload_blob = this.jsPsych.pluginAPI.getVideoBuffer(trial.stimulus[0]);\n if (!video_preload_blob) {\n for (var i = 0; i < trial.stimulus.length; i++) {\n var file_name = trial.stimulus[i];\n if (file_name.indexOf(\"?\") > -1) {\n file_name = file_name.substring(0, file_name.indexOf(\"?\"));\n }\n var type = file_name.substr(file_name.lastIndexOf(\".\") + 1);\n type = type.toLowerCase();\n if (type == \"mov\") {\n console.warn(\n \"Warning: video-keyboard-response plugin does not reliably support .mov files.\"\n );\n }\n video_html += '<source src=\"' + file_name + '\" type=\"video/' + type + '\">';\n }\n }\n video_html += \"</video>\";\n video_html += \"</div>\";\n\n // add prompt if there is one\n if (trial.prompt !== null) {\n video_html += trial.prompt;\n }\n\n display_element.innerHTML = video_html;\n\n var video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n );\n\n if (video_preload_blob) {\n video_element.src = video_preload_blob;\n }\n\n video_element.onended = () => {\n if (trial.trial_ends_after_video) {\n end_trial();\n }\n if (trial.response_allowed_while_playing == false && !trial.trial_ends_after_video) {\n // start keyboard listener\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n video_element.playbackRate = trial.rate;\n\n // if video start time is specified, hide the video and set the starting time\n // before showing and playing, so that the video doesn't automatically show the first frame\n if (trial.start !== null) {\n video_element.pause();\n video_element.onseeked = function () {\n video_element.style.visibility = \"visible\";\n video_element.muted = false;\n if (trial.autoplay) {\n video_element.play();\n } else {\n video_element.pause();\n }\n video_element.onseeked = function () {};\n };\n video_element.onplaying = function () {\n video_element.currentTime = trial.start;\n video_element.onplaying = function () {};\n };\n // fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,\n // change current time, then show/unmute\n video_element.muted = true;\n video_element.play();\n }\n\n let stopped = false;\n if (trial.stop !== null) {\n video_element.addEventListener(\"timeupdate\", function (e) {\n var currenttime = video_element.currentTime;\n if (currenttime >= trial.stop) {\n video_element.pause();\n if (trial.trial_ends_after_video && !stopped) {\n // this is to prevent end_trial from being called twice, because the timeupdate event\n // can fire in quick succession\n stopped = true;\n end_trial();\n }\n }\n });\n }\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // stop the video file if it is playing\n // remove end event listeners if they exist\n display_element\n .querySelector<HTMLVideoElement>(\"#jspsych-video-keyboard-response-stimulus\")\n .pause();\n display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n ).onended = function () {};\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n };\n\n // function to handle responses by the subject\n var after_response = function (info) {\n // after a valid response, the stimulus will have the CSS class 'responded'\n // which can be used to provide visual feedback that a response was recorded\n display_element.querySelector(\"#jspsych-video-keyboard-response-stimulus\").className +=\n \" responded\";\n\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n };\n\n // start the response listener\n if (trial.choices != \"NO_KEYS\" && trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n }\n}\n\nexport default VideoKeyboardResponsePlugin;\n"],"names":["info","name","parameters","stimulus","type","ParameterType","VIDEO","pretty_name","default","undefined","array","choices","KEYS","prompt","HTML_STRING","width","INT","height","autoplay","BOOL","controls","start","FLOAT","stop","rate","trial_ends_after_video","trial_duration","response_ends_trial","response_allowed_while_playing","VideoKeyboardResponsePlugin","constructor","jsPsych","this","trial","display_element","video_html","video_preload_blob","pluginAPI","getVideoBuffer","i","length","file_name","indexOf","substring","substr","lastIndexOf","toLowerCase","console","warn","innerHTML","video_element","querySelector","src","onended","end_trial","getKeyboardResponse","callback_function","after_response","valid_responses","rt_method","persist","allow_held_key","playbackRate","pause","onseeked","style","visibility","muted","play","onplaying","currentTime","stopped","addEventListener","e","response","rt","key","clearAllTimeouts","cancelAllKeyboardResponses","trial_data","finishTrial","className","setTimeout"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.browser.min.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"video-keyboard-response\",\n parameters: {\n /** Array of the video file(s) to play. Video can be provided in multiple file formats for better cross-browser support. */\n stimulus: {\n type: ParameterType.VIDEO,\n pretty_name: \"Video\",\n default: undefined,\n array: true,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The width of the video in pixels. */\n width: {\n type: ParameterType.INT,\n pretty_name: \"Width\",\n default: \"\",\n },\n /** The height of the video display in pixels. */\n height: {\n type: ParameterType.INT,\n pretty_name: \"Height\",\n default: \"\",\n },\n /** If true, the video will begin playing as soon as it has loaded. */\n autoplay: {\n type: ParameterType.BOOL,\n pretty_name: \"Autoplay\",\n default: true,\n },\n /** If true, the subject will be able to pause the video or move the playback to any point in the video. */\n controls: {\n type: ParameterType.BOOL,\n pretty_name: \"Controls\",\n default: false,\n },\n /** Time to start the clip. If null (default), video will start at the beginning of the file. */\n start: {\n type: ParameterType.FLOAT,\n pretty_name: \"Start\",\n default: null,\n },\n /** Time to stop the clip. If null (default), video will stop at the end of the file. */\n stop: {\n type: ParameterType.FLOAT,\n pretty_name: \"Stop\",\n default: null,\n },\n /** The playback rate of the video. 1 is normal, <1 is slower, >1 is faster. */\n rate: {\n type: ParameterType.FLOAT,\n pretty_name: \"Rate\",\n default: 1,\n },\n /** If true, the trial will end immediately after the video finishes playing. */\n trial_ends_after_video: {\n type: ParameterType.BOOL,\n pretty_name: \"End trial after video finishes\",\n default: false,\n },\n /** How long to show trial before it ends. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when subject makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then responses are allowed while the video is playing. If false, then the video must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **video-keyboard-response**\n *\n * jsPsych plugin for playing a video file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-video-keyboard-response/ video-keyboard-response plugin documentation on jspsych.org}\n */\nclass VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // catch mistake where stimuli are not an array\n if (!Array.isArray(trial.stimulus)) {\n throw new Error(`\n The stimulus property for the video-keyboard-response plugin must be an array\n of files. See https://www.jspsych.org/latest/plugins/video-keyboard-response/#parameters\n `);\n }\n\n // setup stimulus\n var video_html = \"<div>\";\n video_html += '<video id=\"jspsych-video-keyboard-response-stimulus\"';\n\n if (trial.width) {\n video_html += ' width=\"' + trial.width + '\"';\n }\n if (trial.height) {\n video_html += ' height=\"' + trial.height + '\"';\n }\n if (trial.autoplay && trial.start == null) {\n // if autoplay is true and the start time is specified, then the video will start automatically\n // via the play() method, rather than the autoplay attribute, to prevent showing the first frame\n video_html += \" autoplay \";\n }\n if (trial.controls) {\n video_html += \" controls \";\n }\n if (trial.start !== null) {\n // hide video element when page loads if the start time is specified,\n // to prevent the video element from showing the first frame\n video_html += ' style=\"visibility: hidden;\"';\n }\n video_html += \">\";\n\n var video_preload_blob = this.jsPsych.pluginAPI.getVideoBuffer(trial.stimulus[0]);\n if (!video_preload_blob) {\n for (var i = 0; i < trial.stimulus.length; i++) {\n var file_name = trial.stimulus[i];\n if (file_name.indexOf(\"?\") > -1) {\n file_name = file_name.substring(0, file_name.indexOf(\"?\"));\n }\n var type = file_name.substr(file_name.lastIndexOf(\".\") + 1);\n type = type.toLowerCase();\n if (type == \"mov\") {\n console.warn(\n \"Warning: video-keyboard-response plugin does not reliably support .mov files.\"\n );\n }\n video_html += '<source src=\"' + file_name + '\" type=\"video/' + type + '\">';\n }\n }\n video_html += \"</video>\";\n video_html += \"</div>\";\n\n // add prompt if there is one\n if (trial.prompt !== null) {\n video_html += trial.prompt;\n }\n\n display_element.innerHTML = video_html;\n\n var video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n );\n\n if (video_preload_blob) {\n video_element.src = video_preload_blob;\n }\n\n video_element.onended = () => {\n if (trial.trial_ends_after_video) {\n end_trial();\n }\n if (trial.response_allowed_while_playing == false && !trial.trial_ends_after_video) {\n // start keyboard listener\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n video_element.playbackRate = trial.rate;\n\n // if video start time is specified, hide the video and set the starting time\n // before showing and playing, so that the video doesn't automatically show the first frame\n if (trial.start !== null) {\n video_element.pause();\n video_element.onseeked = () => {\n video_element.style.visibility = \"visible\";\n video_element.muted = false;\n if (trial.autoplay) {\n video_element.play();\n } else {\n video_element.pause();\n }\n video_element.onseeked = () => {};\n };\n video_element.onplaying = () => {\n video_element.currentTime = trial.start;\n video_element.onplaying = () => {};\n };\n // fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,\n // change current time, then show/unmute\n video_element.muted = true;\n video_element.play();\n }\n\n let stopped = false;\n if (trial.stop !== null) {\n video_element.addEventListener(\"timeupdate\", (e) => {\n var currenttime = video_element.currentTime;\n if (currenttime >= trial.stop) {\n if (!trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n video_element.pause();\n if (trial.trial_ends_after_video && !stopped) {\n // this is to prevent end_trial from being called twice, because the timeupdate event\n // can fire in quick succession\n stopped = true;\n end_trial();\n }\n }\n });\n }\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // stop the video file if it is playing\n // remove end event listeners if they exist\n display_element\n .querySelector<HTMLVideoElement>(\"#jspsych-video-keyboard-response-stimulus\")\n .pause();\n display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n ).onended = () => {};\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n };\n\n // function to handle responses by the subject\n var after_response = (info) => {\n // after a valid response, the stimulus will have the CSS class 'responded'\n // which can be used to provide visual feedback that a response was recorded\n display_element.querySelector(\"#jspsych-video-keyboard-response-stimulus\").className +=\n \" responded\";\n\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n };\n\n // start the response listener\n if (trial.choices != \"NO_KEYS\" && trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration);\n }\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-button-response-stimulus\"\n );\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.pressKey(data.response, data.rt);\n }\n };\n\n if (!trial.response_allowed_while_playing) {\n video_element.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.pluginAPI.getValidKey(trial.choices),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n}\n\nexport default VideoKeyboardResponsePlugin;\n"],"names":["info","name","parameters","stimulus","type","ParameterType","VIDEO","pretty_name","default","undefined","array","choices","KEYS","prompt","HTML_STRING","width","INT","height","autoplay","BOOL","controls","start","FLOAT","stop","rate","trial_ends_after_video","trial_duration","response_ends_trial","response_allowed_while_playing","VideoKeyboardResponsePlugin","constructor","jsPsych","this","trial","display_element","Array","isArray","Error","video_html","video_preload_blob","pluginAPI","getVideoBuffer","i","length","file_name","indexOf","substring","substr","lastIndexOf","toLowerCase","console","warn","innerHTML","video_element","querySelector","src","onended","end_trial","getKeyboardResponse","callback_function","after_response","valid_responses","rt_method","persist","allow_held_key","playbackRate","pause","onseeked","style","visibility","muted","play","onplaying","currentTime","stopped","addEventListener","e","response","rt","key","clearAllTimeouts","cancelAllKeyboardResponses","trial_data","finishTrial","className","setTimeout","simulate","simulation_mode","simulation_options","load_callback","simulate_data_only","simulate_visual","data","create_simulation_data","getDisplayElement","respond","pressKey","default_data","randomization","sampleExGaussian","getValidKey","mergeSimulationData","ensureSimulationDataConsistency"],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -105,6 +105,13 @@ class VideoKeyboardResponsePlugin {
|
|
|
105
105
|
this.jsPsych = jsPsych;
|
|
106
106
|
}
|
|
107
107
|
trial(display_element, trial) {
|
|
108
|
+
// catch mistake where stimuli are not an array
|
|
109
|
+
if (!Array.isArray(trial.stimulus)) {
|
|
110
|
+
throw new Error(`
|
|
111
|
+
The stimulus property for the video-keyboard-response plugin must be an array
|
|
112
|
+
of files. See https://www.jspsych.org/latest/plugins/video-keyboard-response/#parameters
|
|
113
|
+
`);
|
|
114
|
+
}
|
|
108
115
|
// setup stimulus
|
|
109
116
|
var video_html = "<div>";
|
|
110
117
|
video_html += '<video id="jspsych-video-keyboard-response-stimulus"';
|
|
@@ -174,7 +181,7 @@ class VideoKeyboardResponsePlugin {
|
|
|
174
181
|
// before showing and playing, so that the video doesn't automatically show the first frame
|
|
175
182
|
if (trial.start !== null) {
|
|
176
183
|
video_element.pause();
|
|
177
|
-
video_element.onseeked =
|
|
184
|
+
video_element.onseeked = () => {
|
|
178
185
|
video_element.style.visibility = "visible";
|
|
179
186
|
video_element.muted = false;
|
|
180
187
|
if (trial.autoplay) {
|
|
@@ -183,11 +190,11 @@ class VideoKeyboardResponsePlugin {
|
|
|
183
190
|
else {
|
|
184
191
|
video_element.pause();
|
|
185
192
|
}
|
|
186
|
-
video_element.onseeked =
|
|
193
|
+
video_element.onseeked = () => { };
|
|
187
194
|
};
|
|
188
|
-
video_element.onplaying =
|
|
195
|
+
video_element.onplaying = () => {
|
|
189
196
|
video_element.currentTime = trial.start;
|
|
190
|
-
video_element.onplaying =
|
|
197
|
+
video_element.onplaying = () => { };
|
|
191
198
|
};
|
|
192
199
|
// fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,
|
|
193
200
|
// change current time, then show/unmute
|
|
@@ -196,9 +203,18 @@ class VideoKeyboardResponsePlugin {
|
|
|
196
203
|
}
|
|
197
204
|
let stopped = false;
|
|
198
205
|
if (trial.stop !== null) {
|
|
199
|
-
video_element.addEventListener("timeupdate",
|
|
206
|
+
video_element.addEventListener("timeupdate", (e) => {
|
|
200
207
|
var currenttime = video_element.currentTime;
|
|
201
208
|
if (currenttime >= trial.stop) {
|
|
209
|
+
if (!trial.response_allowed_while_playing) {
|
|
210
|
+
this.jsPsych.pluginAPI.getKeyboardResponse({
|
|
211
|
+
callback_function: after_response,
|
|
212
|
+
valid_responses: trial.choices,
|
|
213
|
+
rt_method: "performance",
|
|
214
|
+
persist: false,
|
|
215
|
+
allow_held_key: false,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
202
218
|
video_element.pause();
|
|
203
219
|
if (trial.trial_ends_after_video && !stopped) {
|
|
204
220
|
// this is to prevent end_trial from being called twice, because the timeupdate event
|
|
@@ -225,7 +241,7 @@ class VideoKeyboardResponsePlugin {
|
|
|
225
241
|
display_element
|
|
226
242
|
.querySelector("#jspsych-video-keyboard-response-stimulus")
|
|
227
243
|
.pause();
|
|
228
|
-
display_element.querySelector("#jspsych-video-keyboard-response-stimulus").onended =
|
|
244
|
+
display_element.querySelector("#jspsych-video-keyboard-response-stimulus").onended = () => { };
|
|
229
245
|
// gather the data to store for the trial
|
|
230
246
|
var trial_data = {
|
|
231
247
|
rt: response.rt,
|
|
@@ -238,7 +254,7 @@ class VideoKeyboardResponsePlugin {
|
|
|
238
254
|
this.jsPsych.finishTrial(trial_data);
|
|
239
255
|
};
|
|
240
256
|
// function to handle responses by the subject
|
|
241
|
-
var after_response =
|
|
257
|
+
var after_response = (info) => {
|
|
242
258
|
// after a valid response, the stimulus will have the CSS class 'responded'
|
|
243
259
|
// which can be used to provide visual feedback that a response was recorded
|
|
244
260
|
display_element.querySelector("#jspsych-video-keyboard-response-stimulus").className +=
|
|
@@ -263,10 +279,49 @@ class VideoKeyboardResponsePlugin {
|
|
|
263
279
|
}
|
|
264
280
|
// end trial if time limit is set
|
|
265
281
|
if (trial.trial_duration !== null) {
|
|
266
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
267
|
-
|
|
268
|
-
|
|
282
|
+
this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
286
|
+
if (simulation_mode == "data-only") {
|
|
287
|
+
load_callback();
|
|
288
|
+
this.simulate_data_only(trial, simulation_options);
|
|
289
|
+
}
|
|
290
|
+
if (simulation_mode == "visual") {
|
|
291
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
simulate_data_only(trial, simulation_options) {
|
|
295
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
296
|
+
this.jsPsych.finishTrial(data);
|
|
297
|
+
}
|
|
298
|
+
simulate_visual(trial, simulation_options, load_callback) {
|
|
299
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
300
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
301
|
+
this.trial(display_element, trial);
|
|
302
|
+
load_callback();
|
|
303
|
+
const video_element = display_element.querySelector("#jspsych-video-button-response-stimulus");
|
|
304
|
+
const respond = () => {
|
|
305
|
+
if (data.rt !== null) {
|
|
306
|
+
this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
if (!trial.response_allowed_while_playing) {
|
|
310
|
+
video_element.addEventListener("ended", respond);
|
|
269
311
|
}
|
|
312
|
+
else {
|
|
313
|
+
respond();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
create_simulation_data(trial, simulation_options) {
|
|
317
|
+
const default_data = {
|
|
318
|
+
stimulus: trial.stimulus,
|
|
319
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
320
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
321
|
+
};
|
|
322
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
323
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
324
|
+
return data;
|
|
270
325
|
}
|
|
271
326
|
}
|
|
272
327
|
VideoKeyboardResponsePlugin.info = info;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"video-keyboard-response\",\n parameters: {\n /** Array of the video file(s) to play. Video can be provided in multiple file formats for better cross-browser support. */\n stimulus: {\n type: ParameterType.VIDEO,\n pretty_name: \"Video\",\n default: undefined,\n array: true,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The width of the video in pixels. */\n width: {\n type: ParameterType.INT,\n pretty_name: \"Width\",\n default: \"\",\n },\n /** The height of the video display in pixels. */\n height: {\n type: ParameterType.INT,\n pretty_name: \"Height\",\n default: \"\",\n },\n /** If true, the video will begin playing as soon as it has loaded. */\n autoplay: {\n type: ParameterType.BOOL,\n pretty_name: \"Autoplay\",\n default: true,\n },\n /** If true, the subject will be able to pause the video or move the playback to any point in the video. */\n controls: {\n type: ParameterType.BOOL,\n pretty_name: \"Controls\",\n default: false,\n },\n /** Time to start the clip. If null (default), video will start at the beginning of the file. */\n start: {\n type: ParameterType.FLOAT,\n pretty_name: \"Start\",\n default: null,\n },\n /** Time to stop the clip. If null (default), video will stop at the end of the file. */\n stop: {\n type: ParameterType.FLOAT,\n pretty_name: \"Stop\",\n default: null,\n },\n /** The playback rate of the video. 1 is normal, <1 is slower, >1 is faster. */\n rate: {\n type: ParameterType.FLOAT,\n pretty_name: \"Rate\",\n default: 1,\n },\n /** If true, the trial will end immediately after the video finishes playing. */\n trial_ends_after_video: {\n type: ParameterType.BOOL,\n pretty_name: \"End trial after video finishes\",\n default: false,\n },\n /** How long to show trial before it ends. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when subject makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then responses are allowed while the video is playing. If false, then the video must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **video-keyboard-response**\n *\n * jsPsych plugin for playing a video file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-video-keyboard-response/ video-keyboard-response plugin documentation on jspsych.org}\n */\nclass VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // setup stimulus\n var video_html = \"<div>\";\n video_html += '<video id=\"jspsych-video-keyboard-response-stimulus\"';\n\n if (trial.width) {\n video_html += ' width=\"' + trial.width + '\"';\n }\n if (trial.height) {\n video_html += ' height=\"' + trial.height + '\"';\n }\n if (trial.autoplay && trial.start == null) {\n // if autoplay is true and the start time is specified, then the video will start automatically\n // via the play() method, rather than the autoplay attribute, to prevent showing the first frame\n video_html += \" autoplay \";\n }\n if (trial.controls) {\n video_html += \" controls \";\n }\n if (trial.start !== null) {\n // hide video element when page loads if the start time is specified,\n // to prevent the video element from showing the first frame\n video_html += ' style=\"visibility: hidden;\"';\n }\n video_html += \">\";\n\n var video_preload_blob = this.jsPsych.pluginAPI.getVideoBuffer(trial.stimulus[0]);\n if (!video_preload_blob) {\n for (var i = 0; i < trial.stimulus.length; i++) {\n var file_name = trial.stimulus[i];\n if (file_name.indexOf(\"?\") > -1) {\n file_name = file_name.substring(0, file_name.indexOf(\"?\"));\n }\n var type = file_name.substr(file_name.lastIndexOf(\".\") + 1);\n type = type.toLowerCase();\n if (type == \"mov\") {\n console.warn(\n \"Warning: video-keyboard-response plugin does not reliably support .mov files.\"\n );\n }\n video_html += '<source src=\"' + file_name + '\" type=\"video/' + type + '\">';\n }\n }\n video_html += \"</video>\";\n video_html += \"</div>\";\n\n // add prompt if there is one\n if (trial.prompt !== null) {\n video_html += trial.prompt;\n }\n\n display_element.innerHTML = video_html;\n\n var video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n );\n\n if (video_preload_blob) {\n video_element.src = video_preload_blob;\n }\n\n video_element.onended = () => {\n if (trial.trial_ends_after_video) {\n end_trial();\n }\n if (trial.response_allowed_while_playing == false && !trial.trial_ends_after_video) {\n // start keyboard listener\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n video_element.playbackRate = trial.rate;\n\n // if video start time is specified, hide the video and set the starting time\n // before showing and playing, so that the video doesn't automatically show the first frame\n if (trial.start !== null) {\n video_element.pause();\n video_element.onseeked = function () {\n video_element.style.visibility = \"visible\";\n video_element.muted = false;\n if (trial.autoplay) {\n video_element.play();\n } else {\n video_element.pause();\n }\n video_element.onseeked = function () {};\n };\n video_element.onplaying = function () {\n video_element.currentTime = trial.start;\n video_element.onplaying = function () {};\n };\n // fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,\n // change current time, then show/unmute\n video_element.muted = true;\n video_element.play();\n }\n\n let stopped = false;\n if (trial.stop !== null) {\n video_element.addEventListener(\"timeupdate\", function (e) {\n var currenttime = video_element.currentTime;\n if (currenttime >= trial.stop) {\n video_element.pause();\n if (trial.trial_ends_after_video && !stopped) {\n // this is to prevent end_trial from being called twice, because the timeupdate event\n // can fire in quick succession\n stopped = true;\n end_trial();\n }\n }\n });\n }\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // stop the video file if it is playing\n // remove end event listeners if they exist\n display_element\n .querySelector<HTMLVideoElement>(\"#jspsych-video-keyboard-response-stimulus\")\n .pause();\n display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n ).onended = function () {};\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n };\n\n // function to handle responses by the subject\n var after_response = function (info) {\n // after a valid response, the stimulus will have the CSS class 'responded'\n // which can be used to provide visual feedback that a response was recorded\n display_element.querySelector(\"#jspsych-video-keyboard-response-stimulus\").className +=\n \" responded\";\n\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n };\n\n // start the response listener\n if (trial.choices != \"NO_KEYS\" && trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n }\n}\n\nexport default VideoKeyboardResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"video-keyboard-response\",\n parameters: {\n /** Array of the video file(s) to play. Video can be provided in multiple file formats for better cross-browser support. */\n stimulus: {\n type: ParameterType.VIDEO,\n pretty_name: \"Video\",\n default: undefined,\n array: true,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The width of the video in pixels. */\n width: {\n type: ParameterType.INT,\n pretty_name: \"Width\",\n default: \"\",\n },\n /** The height of the video display in pixels. */\n height: {\n type: ParameterType.INT,\n pretty_name: \"Height\",\n default: \"\",\n },\n /** If true, the video will begin playing as soon as it has loaded. */\n autoplay: {\n type: ParameterType.BOOL,\n pretty_name: \"Autoplay\",\n default: true,\n },\n /** If true, the subject will be able to pause the video or move the playback to any point in the video. */\n controls: {\n type: ParameterType.BOOL,\n pretty_name: \"Controls\",\n default: false,\n },\n /** Time to start the clip. If null (default), video will start at the beginning of the file. */\n start: {\n type: ParameterType.FLOAT,\n pretty_name: \"Start\",\n default: null,\n },\n /** Time to stop the clip. If null (default), video will stop at the end of the file. */\n stop: {\n type: ParameterType.FLOAT,\n pretty_name: \"Stop\",\n default: null,\n },\n /** The playback rate of the video. 1 is normal, <1 is slower, >1 is faster. */\n rate: {\n type: ParameterType.FLOAT,\n pretty_name: \"Rate\",\n default: 1,\n },\n /** If true, the trial will end immediately after the video finishes playing. */\n trial_ends_after_video: {\n type: ParameterType.BOOL,\n pretty_name: \"End trial after video finishes\",\n default: false,\n },\n /** How long to show trial before it ends. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when subject makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then responses are allowed while the video is playing. If false, then the video must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **video-keyboard-response**\n *\n * jsPsych plugin for playing a video file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-video-keyboard-response/ video-keyboard-response plugin documentation on jspsych.org}\n */\nclass VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // catch mistake where stimuli are not an array\n if (!Array.isArray(trial.stimulus)) {\n throw new Error(`\n The stimulus property for the video-keyboard-response plugin must be an array\n of files. See https://www.jspsych.org/latest/plugins/video-keyboard-response/#parameters\n `);\n }\n\n // setup stimulus\n var video_html = \"<div>\";\n video_html += '<video id=\"jspsych-video-keyboard-response-stimulus\"';\n\n if (trial.width) {\n video_html += ' width=\"' + trial.width + '\"';\n }\n if (trial.height) {\n video_html += ' height=\"' + trial.height + '\"';\n }\n if (trial.autoplay && trial.start == null) {\n // if autoplay is true and the start time is specified, then the video will start automatically\n // via the play() method, rather than the autoplay attribute, to prevent showing the first frame\n video_html += \" autoplay \";\n }\n if (trial.controls) {\n video_html += \" controls \";\n }\n if (trial.start !== null) {\n // hide video element when page loads if the start time is specified,\n // to prevent the video element from showing the first frame\n video_html += ' style=\"visibility: hidden;\"';\n }\n video_html += \">\";\n\n var video_preload_blob = this.jsPsych.pluginAPI.getVideoBuffer(trial.stimulus[0]);\n if (!video_preload_blob) {\n for (var i = 0; i < trial.stimulus.length; i++) {\n var file_name = trial.stimulus[i];\n if (file_name.indexOf(\"?\") > -1) {\n file_name = file_name.substring(0, file_name.indexOf(\"?\"));\n }\n var type = file_name.substr(file_name.lastIndexOf(\".\") + 1);\n type = type.toLowerCase();\n if (type == \"mov\") {\n console.warn(\n \"Warning: video-keyboard-response plugin does not reliably support .mov files.\"\n );\n }\n video_html += '<source src=\"' + file_name + '\" type=\"video/' + type + '\">';\n }\n }\n video_html += \"</video>\";\n video_html += \"</div>\";\n\n // add prompt if there is one\n if (trial.prompt !== null) {\n video_html += trial.prompt;\n }\n\n display_element.innerHTML = video_html;\n\n var video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n );\n\n if (video_preload_blob) {\n video_element.src = video_preload_blob;\n }\n\n video_element.onended = () => {\n if (trial.trial_ends_after_video) {\n end_trial();\n }\n if (trial.response_allowed_while_playing == false && !trial.trial_ends_after_video) {\n // start keyboard listener\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n video_element.playbackRate = trial.rate;\n\n // if video start time is specified, hide the video and set the starting time\n // before showing and playing, so that the video doesn't automatically show the first frame\n if (trial.start !== null) {\n video_element.pause();\n video_element.onseeked = () => {\n video_element.style.visibility = \"visible\";\n video_element.muted = false;\n if (trial.autoplay) {\n video_element.play();\n } else {\n video_element.pause();\n }\n video_element.onseeked = () => {};\n };\n video_element.onplaying = () => {\n video_element.currentTime = trial.start;\n video_element.onplaying = () => {};\n };\n // fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,\n // change current time, then show/unmute\n video_element.muted = true;\n video_element.play();\n }\n\n let stopped = false;\n if (trial.stop !== null) {\n video_element.addEventListener(\"timeupdate\", (e) => {\n var currenttime = video_element.currentTime;\n if (currenttime >= trial.stop) {\n if (!trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n video_element.pause();\n if (trial.trial_ends_after_video && !stopped) {\n // this is to prevent end_trial from being called twice, because the timeupdate event\n // can fire in quick succession\n stopped = true;\n end_trial();\n }\n }\n });\n }\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // stop the video file if it is playing\n // remove end event listeners if they exist\n display_element\n .querySelector<HTMLVideoElement>(\"#jspsych-video-keyboard-response-stimulus\")\n .pause();\n display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n ).onended = () => {};\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n };\n\n // function to handle responses by the subject\n var after_response = (info) => {\n // after a valid response, the stimulus will have the CSS class 'responded'\n // which can be used to provide visual feedback that a response was recorded\n display_element.querySelector(\"#jspsych-video-keyboard-response-stimulus\").className +=\n \" responded\";\n\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n };\n\n // start the response listener\n if (trial.choices != \"NO_KEYS\" && trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration);\n }\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-button-response-stimulus\"\n );\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.pressKey(data.response, data.rt);\n }\n };\n\n if (!trial.response_allowed_while_playing) {\n video_element.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.pluginAPI.getValidKey(trial.choices),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n}\n\nexport default VideoKeyboardResponsePlugin;\n"],"names":["ParameterType"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -192,5 +192,9 @@ declare class VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
192
192
|
};
|
|
193
193
|
constructor(jsPsych: JsPsych);
|
|
194
194
|
trial(display_element: HTMLElement, trial: TrialType<Info>): void;
|
|
195
|
+
simulate(trial: TrialType<Info>, simulation_mode: any, simulation_options: any, load_callback: () => void): void;
|
|
196
|
+
private simulate_data_only;
|
|
197
|
+
private simulate_visual;
|
|
198
|
+
private create_simulation_data;
|
|
195
199
|
}
|
|
196
200
|
export default VideoKeyboardResponsePlugin;
|
package/dist/index.js
CHANGED
|
@@ -103,6 +103,13 @@ class VideoKeyboardResponsePlugin {
|
|
|
103
103
|
this.jsPsych = jsPsych;
|
|
104
104
|
}
|
|
105
105
|
trial(display_element, trial) {
|
|
106
|
+
// catch mistake where stimuli are not an array
|
|
107
|
+
if (!Array.isArray(trial.stimulus)) {
|
|
108
|
+
throw new Error(`
|
|
109
|
+
The stimulus property for the video-keyboard-response plugin must be an array
|
|
110
|
+
of files. See https://www.jspsych.org/latest/plugins/video-keyboard-response/#parameters
|
|
111
|
+
`);
|
|
112
|
+
}
|
|
106
113
|
// setup stimulus
|
|
107
114
|
var video_html = "<div>";
|
|
108
115
|
video_html += '<video id="jspsych-video-keyboard-response-stimulus"';
|
|
@@ -172,7 +179,7 @@ class VideoKeyboardResponsePlugin {
|
|
|
172
179
|
// before showing and playing, so that the video doesn't automatically show the first frame
|
|
173
180
|
if (trial.start !== null) {
|
|
174
181
|
video_element.pause();
|
|
175
|
-
video_element.onseeked =
|
|
182
|
+
video_element.onseeked = () => {
|
|
176
183
|
video_element.style.visibility = "visible";
|
|
177
184
|
video_element.muted = false;
|
|
178
185
|
if (trial.autoplay) {
|
|
@@ -181,11 +188,11 @@ class VideoKeyboardResponsePlugin {
|
|
|
181
188
|
else {
|
|
182
189
|
video_element.pause();
|
|
183
190
|
}
|
|
184
|
-
video_element.onseeked =
|
|
191
|
+
video_element.onseeked = () => { };
|
|
185
192
|
};
|
|
186
|
-
video_element.onplaying =
|
|
193
|
+
video_element.onplaying = () => {
|
|
187
194
|
video_element.currentTime = trial.start;
|
|
188
|
-
video_element.onplaying =
|
|
195
|
+
video_element.onplaying = () => { };
|
|
189
196
|
};
|
|
190
197
|
// fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,
|
|
191
198
|
// change current time, then show/unmute
|
|
@@ -194,9 +201,18 @@ class VideoKeyboardResponsePlugin {
|
|
|
194
201
|
}
|
|
195
202
|
let stopped = false;
|
|
196
203
|
if (trial.stop !== null) {
|
|
197
|
-
video_element.addEventListener("timeupdate",
|
|
204
|
+
video_element.addEventListener("timeupdate", (e) => {
|
|
198
205
|
var currenttime = video_element.currentTime;
|
|
199
206
|
if (currenttime >= trial.stop) {
|
|
207
|
+
if (!trial.response_allowed_while_playing) {
|
|
208
|
+
this.jsPsych.pluginAPI.getKeyboardResponse({
|
|
209
|
+
callback_function: after_response,
|
|
210
|
+
valid_responses: trial.choices,
|
|
211
|
+
rt_method: "performance",
|
|
212
|
+
persist: false,
|
|
213
|
+
allow_held_key: false,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
200
216
|
video_element.pause();
|
|
201
217
|
if (trial.trial_ends_after_video && !stopped) {
|
|
202
218
|
// this is to prevent end_trial from being called twice, because the timeupdate event
|
|
@@ -223,7 +239,7 @@ class VideoKeyboardResponsePlugin {
|
|
|
223
239
|
display_element
|
|
224
240
|
.querySelector("#jspsych-video-keyboard-response-stimulus")
|
|
225
241
|
.pause();
|
|
226
|
-
display_element.querySelector("#jspsych-video-keyboard-response-stimulus").onended =
|
|
242
|
+
display_element.querySelector("#jspsych-video-keyboard-response-stimulus").onended = () => { };
|
|
227
243
|
// gather the data to store for the trial
|
|
228
244
|
var trial_data = {
|
|
229
245
|
rt: response.rt,
|
|
@@ -236,7 +252,7 @@ class VideoKeyboardResponsePlugin {
|
|
|
236
252
|
this.jsPsych.finishTrial(trial_data);
|
|
237
253
|
};
|
|
238
254
|
// function to handle responses by the subject
|
|
239
|
-
var after_response =
|
|
255
|
+
var after_response = (info) => {
|
|
240
256
|
// after a valid response, the stimulus will have the CSS class 'responded'
|
|
241
257
|
// which can be used to provide visual feedback that a response was recorded
|
|
242
258
|
display_element.querySelector("#jspsych-video-keyboard-response-stimulus").className +=
|
|
@@ -261,10 +277,49 @@ class VideoKeyboardResponsePlugin {
|
|
|
261
277
|
}
|
|
262
278
|
// end trial if time limit is set
|
|
263
279
|
if (trial.trial_duration !== null) {
|
|
264
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
265
|
-
|
|
266
|
-
|
|
280
|
+
this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
simulate(trial, simulation_mode, simulation_options, load_callback) {
|
|
284
|
+
if (simulation_mode == "data-only") {
|
|
285
|
+
load_callback();
|
|
286
|
+
this.simulate_data_only(trial, simulation_options);
|
|
287
|
+
}
|
|
288
|
+
if (simulation_mode == "visual") {
|
|
289
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
simulate_data_only(trial, simulation_options) {
|
|
293
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
294
|
+
this.jsPsych.finishTrial(data);
|
|
295
|
+
}
|
|
296
|
+
simulate_visual(trial, simulation_options, load_callback) {
|
|
297
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
298
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
299
|
+
this.trial(display_element, trial);
|
|
300
|
+
load_callback();
|
|
301
|
+
const video_element = display_element.querySelector("#jspsych-video-button-response-stimulus");
|
|
302
|
+
const respond = () => {
|
|
303
|
+
if (data.rt !== null) {
|
|
304
|
+
this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
if (!trial.response_allowed_while_playing) {
|
|
308
|
+
video_element.addEventListener("ended", respond);
|
|
267
309
|
}
|
|
310
|
+
else {
|
|
311
|
+
respond();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
create_simulation_data(trial, simulation_options) {
|
|
315
|
+
const default_data = {
|
|
316
|
+
stimulus: trial.stimulus,
|
|
317
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
318
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
319
|
+
};
|
|
320
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
321
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
322
|
+
return data;
|
|
268
323
|
}
|
|
269
324
|
}
|
|
270
325
|
VideoKeyboardResponsePlugin.info = info;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"video-keyboard-response\",\n parameters: {\n /** Array of the video file(s) to play. Video can be provided in multiple file formats for better cross-browser support. */\n stimulus: {\n type: ParameterType.VIDEO,\n pretty_name: \"Video\",\n default: undefined,\n array: true,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The width of the video in pixels. */\n width: {\n type: ParameterType.INT,\n pretty_name: \"Width\",\n default: \"\",\n },\n /** The height of the video display in pixels. */\n height: {\n type: ParameterType.INT,\n pretty_name: \"Height\",\n default: \"\",\n },\n /** If true, the video will begin playing as soon as it has loaded. */\n autoplay: {\n type: ParameterType.BOOL,\n pretty_name: \"Autoplay\",\n default: true,\n },\n /** If true, the subject will be able to pause the video or move the playback to any point in the video. */\n controls: {\n type: ParameterType.BOOL,\n pretty_name: \"Controls\",\n default: false,\n },\n /** Time to start the clip. If null (default), video will start at the beginning of the file. */\n start: {\n type: ParameterType.FLOAT,\n pretty_name: \"Start\",\n default: null,\n },\n /** Time to stop the clip. If null (default), video will stop at the end of the file. */\n stop: {\n type: ParameterType.FLOAT,\n pretty_name: \"Stop\",\n default: null,\n },\n /** The playback rate of the video. 1 is normal, <1 is slower, >1 is faster. */\n rate: {\n type: ParameterType.FLOAT,\n pretty_name: \"Rate\",\n default: 1,\n },\n /** If true, the trial will end immediately after the video finishes playing. */\n trial_ends_after_video: {\n type: ParameterType.BOOL,\n pretty_name: \"End trial after video finishes\",\n default: false,\n },\n /** How long to show trial before it ends. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when subject makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then responses are allowed while the video is playing. If false, then the video must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **video-keyboard-response**\n *\n * jsPsych plugin for playing a video file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-video-keyboard-response/ video-keyboard-response plugin documentation on jspsych.org}\n */\nclass VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // setup stimulus\n var video_html = \"<div>\";\n video_html += '<video id=\"jspsych-video-keyboard-response-stimulus\"';\n\n if (trial.width) {\n video_html += ' width=\"' + trial.width + '\"';\n }\n if (trial.height) {\n video_html += ' height=\"' + trial.height + '\"';\n }\n if (trial.autoplay && trial.start == null) {\n // if autoplay is true and the start time is specified, then the video will start automatically\n // via the play() method, rather than the autoplay attribute, to prevent showing the first frame\n video_html += \" autoplay \";\n }\n if (trial.controls) {\n video_html += \" controls \";\n }\n if (trial.start !== null) {\n // hide video element when page loads if the start time is specified,\n // to prevent the video element from showing the first frame\n video_html += ' style=\"visibility: hidden;\"';\n }\n video_html += \">\";\n\n var video_preload_blob = this.jsPsych.pluginAPI.getVideoBuffer(trial.stimulus[0]);\n if (!video_preload_blob) {\n for (var i = 0; i < trial.stimulus.length; i++) {\n var file_name = trial.stimulus[i];\n if (file_name.indexOf(\"?\") > -1) {\n file_name = file_name.substring(0, file_name.indexOf(\"?\"));\n }\n var type = file_name.substr(file_name.lastIndexOf(\".\") + 1);\n type = type.toLowerCase();\n if (type == \"mov\") {\n console.warn(\n \"Warning: video-keyboard-response plugin does not reliably support .mov files.\"\n );\n }\n video_html += '<source src=\"' + file_name + '\" type=\"video/' + type + '\">';\n }\n }\n video_html += \"</video>\";\n video_html += \"</div>\";\n\n // add prompt if there is one\n if (trial.prompt !== null) {\n video_html += trial.prompt;\n }\n\n display_element.innerHTML = video_html;\n\n var video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n );\n\n if (video_preload_blob) {\n video_element.src = video_preload_blob;\n }\n\n video_element.onended = () => {\n if (trial.trial_ends_after_video) {\n end_trial();\n }\n if (trial.response_allowed_while_playing == false && !trial.trial_ends_after_video) {\n // start keyboard listener\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n video_element.playbackRate = trial.rate;\n\n // if video start time is specified, hide the video and set the starting time\n // before showing and playing, so that the video doesn't automatically show the first frame\n if (trial.start !== null) {\n video_element.pause();\n video_element.onseeked = function () {\n video_element.style.visibility = \"visible\";\n video_element.muted = false;\n if (trial.autoplay) {\n video_element.play();\n } else {\n video_element.pause();\n }\n video_element.onseeked = function () {};\n };\n video_element.onplaying = function () {\n video_element.currentTime = trial.start;\n video_element.onplaying = function () {};\n };\n // fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,\n // change current time, then show/unmute\n video_element.muted = true;\n video_element.play();\n }\n\n let stopped = false;\n if (trial.stop !== null) {\n video_element.addEventListener(\"timeupdate\", function (e) {\n var currenttime = video_element.currentTime;\n if (currenttime >= trial.stop) {\n video_element.pause();\n if (trial.trial_ends_after_video && !stopped) {\n // this is to prevent end_trial from being called twice, because the timeupdate event\n // can fire in quick succession\n stopped = true;\n end_trial();\n }\n }\n });\n }\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // stop the video file if it is playing\n // remove end event listeners if they exist\n display_element\n .querySelector<HTMLVideoElement>(\"#jspsych-video-keyboard-response-stimulus\")\n .pause();\n display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n ).onended = function () {};\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n };\n\n // function to handle responses by the subject\n var after_response = function (info) {\n // after a valid response, the stimulus will have the CSS class 'responded'\n // which can be used to provide visual feedback that a response was recorded\n display_element.querySelector(\"#jspsych-video-keyboard-response-stimulus\").className +=\n \" responded\";\n\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n };\n\n // start the response listener\n if (trial.choices != \"NO_KEYS\" && trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(function () {\n end_trial();\n }, trial.trial_duration);\n }\n }\n}\n\nexport default VideoKeyboardResponsePlugin;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nconst info = <const>{\n name: \"video-keyboard-response\",\n parameters: {\n /** Array of the video file(s) to play. Video can be provided in multiple file formats for better cross-browser support. */\n stimulus: {\n type: ParameterType.VIDEO,\n pretty_name: \"Video\",\n default: undefined,\n array: true,\n },\n /** Array containing the key(s) the subject is allowed to press to respond to the stimulus. */\n choices: {\n type: ParameterType.KEYS,\n pretty_name: \"Choices\",\n default: \"ALL_KEYS\",\n },\n /** Any content here will be displayed below the stimulus. */\n prompt: {\n type: ParameterType.HTML_STRING,\n pretty_name: \"Prompt\",\n default: null,\n },\n /** The width of the video in pixels. */\n width: {\n type: ParameterType.INT,\n pretty_name: \"Width\",\n default: \"\",\n },\n /** The height of the video display in pixels. */\n height: {\n type: ParameterType.INT,\n pretty_name: \"Height\",\n default: \"\",\n },\n /** If true, the video will begin playing as soon as it has loaded. */\n autoplay: {\n type: ParameterType.BOOL,\n pretty_name: \"Autoplay\",\n default: true,\n },\n /** If true, the subject will be able to pause the video or move the playback to any point in the video. */\n controls: {\n type: ParameterType.BOOL,\n pretty_name: \"Controls\",\n default: false,\n },\n /** Time to start the clip. If null (default), video will start at the beginning of the file. */\n start: {\n type: ParameterType.FLOAT,\n pretty_name: \"Start\",\n default: null,\n },\n /** Time to stop the clip. If null (default), video will stop at the end of the file. */\n stop: {\n type: ParameterType.FLOAT,\n pretty_name: \"Stop\",\n default: null,\n },\n /** The playback rate of the video. 1 is normal, <1 is slower, >1 is faster. */\n rate: {\n type: ParameterType.FLOAT,\n pretty_name: \"Rate\",\n default: 1,\n },\n /** If true, the trial will end immediately after the video finishes playing. */\n trial_ends_after_video: {\n type: ParameterType.BOOL,\n pretty_name: \"End trial after video finishes\",\n default: false,\n },\n /** How long to show trial before it ends. */\n trial_duration: {\n type: ParameterType.INT,\n pretty_name: \"Trial duration\",\n default: null,\n },\n /** If true, the trial will end when subject makes a response. */\n response_ends_trial: {\n type: ParameterType.BOOL,\n pretty_name: \"Response ends trial\",\n default: true,\n },\n /** If true, then responses are allowed while the video is playing. If false, then the video must finish playing before a response is accepted. */\n response_allowed_while_playing: {\n type: ParameterType.BOOL,\n pretty_name: \"Response allowed while playing\",\n default: true,\n },\n },\n};\n\ntype Info = typeof info;\n\n/**\n * **video-keyboard-response**\n *\n * jsPsych plugin for playing a video file and getting a keyboard response\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/plugins/jspsych-video-keyboard-response/ video-keyboard-response plugin documentation on jspsych.org}\n */\nclass VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // catch mistake where stimuli are not an array\n if (!Array.isArray(trial.stimulus)) {\n throw new Error(`\n The stimulus property for the video-keyboard-response plugin must be an array\n of files. See https://www.jspsych.org/latest/plugins/video-keyboard-response/#parameters\n `);\n }\n\n // setup stimulus\n var video_html = \"<div>\";\n video_html += '<video id=\"jspsych-video-keyboard-response-stimulus\"';\n\n if (trial.width) {\n video_html += ' width=\"' + trial.width + '\"';\n }\n if (trial.height) {\n video_html += ' height=\"' + trial.height + '\"';\n }\n if (trial.autoplay && trial.start == null) {\n // if autoplay is true and the start time is specified, then the video will start automatically\n // via the play() method, rather than the autoplay attribute, to prevent showing the first frame\n video_html += \" autoplay \";\n }\n if (trial.controls) {\n video_html += \" controls \";\n }\n if (trial.start !== null) {\n // hide video element when page loads if the start time is specified,\n // to prevent the video element from showing the first frame\n video_html += ' style=\"visibility: hidden;\"';\n }\n video_html += \">\";\n\n var video_preload_blob = this.jsPsych.pluginAPI.getVideoBuffer(trial.stimulus[0]);\n if (!video_preload_blob) {\n for (var i = 0; i < trial.stimulus.length; i++) {\n var file_name = trial.stimulus[i];\n if (file_name.indexOf(\"?\") > -1) {\n file_name = file_name.substring(0, file_name.indexOf(\"?\"));\n }\n var type = file_name.substr(file_name.lastIndexOf(\".\") + 1);\n type = type.toLowerCase();\n if (type == \"mov\") {\n console.warn(\n \"Warning: video-keyboard-response plugin does not reliably support .mov files.\"\n );\n }\n video_html += '<source src=\"' + file_name + '\" type=\"video/' + type + '\">';\n }\n }\n video_html += \"</video>\";\n video_html += \"</div>\";\n\n // add prompt if there is one\n if (trial.prompt !== null) {\n video_html += trial.prompt;\n }\n\n display_element.innerHTML = video_html;\n\n var video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n );\n\n if (video_preload_blob) {\n video_element.src = video_preload_blob;\n }\n\n video_element.onended = () => {\n if (trial.trial_ends_after_video) {\n end_trial();\n }\n if (trial.response_allowed_while_playing == false && !trial.trial_ends_after_video) {\n // start keyboard listener\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n };\n\n video_element.playbackRate = trial.rate;\n\n // if video start time is specified, hide the video and set the starting time\n // before showing and playing, so that the video doesn't automatically show the first frame\n if (trial.start !== null) {\n video_element.pause();\n video_element.onseeked = () => {\n video_element.style.visibility = \"visible\";\n video_element.muted = false;\n if (trial.autoplay) {\n video_element.play();\n } else {\n video_element.pause();\n }\n video_element.onseeked = () => {};\n };\n video_element.onplaying = () => {\n video_element.currentTime = trial.start;\n video_element.onplaying = () => {};\n };\n // fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,\n // change current time, then show/unmute\n video_element.muted = true;\n video_element.play();\n }\n\n let stopped = false;\n if (trial.stop !== null) {\n video_element.addEventListener(\"timeupdate\", (e) => {\n var currenttime = video_element.currentTime;\n if (currenttime >= trial.stop) {\n if (!trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n video_element.pause();\n if (trial.trial_ends_after_video && !stopped) {\n // this is to prevent end_trial from being called twice, because the timeupdate event\n // can fire in quick succession\n stopped = true;\n end_trial();\n }\n }\n });\n }\n\n // store response\n var response = {\n rt: null,\n key: null,\n };\n\n // function to end trial when it is time\n const end_trial = () => {\n // kill any remaining setTimeout handlers\n this.jsPsych.pluginAPI.clearAllTimeouts();\n\n // kill keyboard listeners\n this.jsPsych.pluginAPI.cancelAllKeyboardResponses();\n\n // stop the video file if it is playing\n // remove end event listeners if they exist\n display_element\n .querySelector<HTMLVideoElement>(\"#jspsych-video-keyboard-response-stimulus\")\n .pause();\n display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-keyboard-response-stimulus\"\n ).onended = () => {};\n\n // gather the data to store for the trial\n var trial_data = {\n rt: response.rt,\n stimulus: trial.stimulus,\n response: response.key,\n };\n\n // clear the display\n display_element.innerHTML = \"\";\n\n // move on to the next trial\n this.jsPsych.finishTrial(trial_data);\n };\n\n // function to handle responses by the subject\n var after_response = (info) => {\n // after a valid response, the stimulus will have the CSS class 'responded'\n // which can be used to provide visual feedback that a response was recorded\n display_element.querySelector(\"#jspsych-video-keyboard-response-stimulus\").className +=\n \" responded\";\n\n // only record the first response\n if (response.key == null) {\n response = info;\n }\n\n if (trial.response_ends_trial) {\n end_trial();\n }\n };\n\n // start the response listener\n if (trial.choices != \"NO_KEYS\" && trial.response_allowed_while_playing) {\n var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({\n callback_function: after_response,\n valid_responses: trial.choices,\n rt_method: \"performance\",\n persist: false,\n allow_held_key: false,\n });\n }\n\n // end trial if time limit is set\n if (trial.trial_duration !== null) {\n this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration);\n }\n }\n\n simulate(\n trial: TrialType<Info>,\n simulation_mode,\n simulation_options: any,\n load_callback: () => void\n ) {\n if (simulation_mode == \"data-only\") {\n load_callback();\n this.simulate_data_only(trial, simulation_options);\n }\n if (simulation_mode == \"visual\") {\n this.simulate_visual(trial, simulation_options, load_callback);\n }\n }\n\n private simulate_data_only(trial: TrialType<Info>, simulation_options) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n this.jsPsych.finishTrial(data);\n }\n\n private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {\n const data = this.create_simulation_data(trial, simulation_options);\n\n const display_element = this.jsPsych.getDisplayElement();\n\n this.trial(display_element, trial);\n load_callback();\n\n const video_element = display_element.querySelector<HTMLVideoElement>(\n \"#jspsych-video-button-response-stimulus\"\n );\n\n const respond = () => {\n if (data.rt !== null) {\n this.jsPsych.pluginAPI.pressKey(data.response, data.rt);\n }\n };\n\n if (!trial.response_allowed_while_playing) {\n video_element.addEventListener(\"ended\", respond);\n } else {\n respond();\n }\n }\n\n private create_simulation_data(trial: TrialType<Info>, simulation_options) {\n const default_data = {\n stimulus: trial.stimulus,\n rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),\n response: this.jsPsych.pluginAPI.getValidKey(trial.choices),\n };\n\n const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);\n\n this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);\n\n return data;\n }\n}\n\nexport default VideoKeyboardResponsePlugin;\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jspsych/plugin-video-keyboard-response",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "jsPsych plugin for playing a video file and getting a keyboard response",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
},
|
|
35
35
|
"homepage": "https://www.jspsych.org/latest/plugins/video-keyboard-response",
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"jspsych": ">=7.
|
|
37
|
+
"jspsych": ">=7.1.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@jspsych/config": "^1.
|
|
41
|
-
"@jspsych/test-utils": "^1.
|
|
40
|
+
"@jspsych/config": "^1.1.0",
|
|
41
|
+
"@jspsych/test-utils": "^1.1.0"
|
|
42
42
|
}
|
|
43
43
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { pressKey, simulateTimeline, startTimeline } from "@jspsych/test-utils";
|
|
2
|
+
import { initJsPsych } from "jspsych";
|
|
3
|
+
|
|
4
|
+
import videoKeyboardResponse from ".";
|
|
5
|
+
|
|
6
|
+
jest.useFakeTimers();
|
|
7
|
+
|
|
8
|
+
beforeAll(() => {
|
|
9
|
+
window.HTMLMediaElement.prototype.pause = () => {};
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// I can't figure out how to get this tested with jest
|
|
13
|
+
describe("video-keyboard-response", () => {
|
|
14
|
+
test("throws error when stimulus is not array #1537", async () => {
|
|
15
|
+
const jsPsych = initJsPsych();
|
|
16
|
+
|
|
17
|
+
const timeline = [
|
|
18
|
+
{
|
|
19
|
+
type: videoKeyboardResponse,
|
|
20
|
+
stimulus: "foo.mp4",
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
await expect(async () => {
|
|
25
|
+
await jsPsych.run(timeline);
|
|
26
|
+
}).rejects.toThrowError();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
describe("video-keyboard-response simulation", () => {
|
|
31
|
+
test("data mode works", async () => {
|
|
32
|
+
const timeline = [
|
|
33
|
+
{
|
|
34
|
+
type: videoKeyboardResponse,
|
|
35
|
+
stimulus: ["foo.mp4"],
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const { expectFinished, getData } = await simulateTimeline(timeline);
|
|
40
|
+
|
|
41
|
+
await expectFinished();
|
|
42
|
+
|
|
43
|
+
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
|
44
|
+
expect(typeof getData().values()[0].response).toBe("string");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// can't run this until we mock video elements.
|
|
48
|
+
test("visual mode works", async () => {
|
|
49
|
+
const jsPsych = initJsPsych();
|
|
50
|
+
|
|
51
|
+
const timeline = [
|
|
52
|
+
{
|
|
53
|
+
type: videoKeyboardResponse,
|
|
54
|
+
stimulus: ["foo.mp4"],
|
|
55
|
+
prompt: "foo",
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const { expectFinished, expectRunning, getHTML, getData } = await simulateTimeline(
|
|
60
|
+
timeline,
|
|
61
|
+
"visual",
|
|
62
|
+
{},
|
|
63
|
+
jsPsych
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
await expectRunning();
|
|
67
|
+
|
|
68
|
+
expect(getHTML()).toContain("foo");
|
|
69
|
+
|
|
70
|
+
jest.runAllTimers();
|
|
71
|
+
|
|
72
|
+
await expectFinished();
|
|
73
|
+
|
|
74
|
+
expect(getData().values()[0].rt).toBeGreaterThan(0);
|
|
75
|
+
expect(typeof getData().values()[0].response).toBe("string");
|
|
76
|
+
});
|
|
77
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -107,6 +107,14 @@ class VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
107
107
|
constructor(private jsPsych: JsPsych) {}
|
|
108
108
|
|
|
109
109
|
trial(display_element: HTMLElement, trial: TrialType<Info>) {
|
|
110
|
+
// catch mistake where stimuli are not an array
|
|
111
|
+
if (!Array.isArray(trial.stimulus)) {
|
|
112
|
+
throw new Error(`
|
|
113
|
+
The stimulus property for the video-keyboard-response plugin must be an array
|
|
114
|
+
of files. See https://www.jspsych.org/latest/plugins/video-keyboard-response/#parameters
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
|
|
110
118
|
// setup stimulus
|
|
111
119
|
var video_html = "<div>";
|
|
112
120
|
video_html += '<video id="jspsych-video-keyboard-response-stimulus"';
|
|
@@ -189,7 +197,7 @@ class VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
189
197
|
// before showing and playing, so that the video doesn't automatically show the first frame
|
|
190
198
|
if (trial.start !== null) {
|
|
191
199
|
video_element.pause();
|
|
192
|
-
video_element.onseeked =
|
|
200
|
+
video_element.onseeked = () => {
|
|
193
201
|
video_element.style.visibility = "visible";
|
|
194
202
|
video_element.muted = false;
|
|
195
203
|
if (trial.autoplay) {
|
|
@@ -197,11 +205,11 @@ class VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
197
205
|
} else {
|
|
198
206
|
video_element.pause();
|
|
199
207
|
}
|
|
200
|
-
video_element.onseeked =
|
|
208
|
+
video_element.onseeked = () => {};
|
|
201
209
|
};
|
|
202
|
-
video_element.onplaying =
|
|
210
|
+
video_element.onplaying = () => {
|
|
203
211
|
video_element.currentTime = trial.start;
|
|
204
|
-
video_element.onplaying =
|
|
212
|
+
video_element.onplaying = () => {};
|
|
205
213
|
};
|
|
206
214
|
// fix for iOS/MacOS browsers: videos aren't seekable until they start playing, so need to hide/mute, play,
|
|
207
215
|
// change current time, then show/unmute
|
|
@@ -211,9 +219,18 @@ class VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
211
219
|
|
|
212
220
|
let stopped = false;
|
|
213
221
|
if (trial.stop !== null) {
|
|
214
|
-
video_element.addEventListener("timeupdate",
|
|
222
|
+
video_element.addEventListener("timeupdate", (e) => {
|
|
215
223
|
var currenttime = video_element.currentTime;
|
|
216
224
|
if (currenttime >= trial.stop) {
|
|
225
|
+
if (!trial.response_allowed_while_playing) {
|
|
226
|
+
var keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({
|
|
227
|
+
callback_function: after_response,
|
|
228
|
+
valid_responses: trial.choices,
|
|
229
|
+
rt_method: "performance",
|
|
230
|
+
persist: false,
|
|
231
|
+
allow_held_key: false,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
217
234
|
video_element.pause();
|
|
218
235
|
if (trial.trial_ends_after_video && !stopped) {
|
|
219
236
|
// this is to prevent end_trial from being called twice, because the timeupdate event
|
|
@@ -246,7 +263,7 @@ class VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
246
263
|
.pause();
|
|
247
264
|
display_element.querySelector<HTMLVideoElement>(
|
|
248
265
|
"#jspsych-video-keyboard-response-stimulus"
|
|
249
|
-
).onended =
|
|
266
|
+
).onended = () => {};
|
|
250
267
|
|
|
251
268
|
// gather the data to store for the trial
|
|
252
269
|
var trial_data = {
|
|
@@ -263,7 +280,7 @@ class VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
263
280
|
};
|
|
264
281
|
|
|
265
282
|
// function to handle responses by the subject
|
|
266
|
-
var after_response =
|
|
283
|
+
var after_response = (info) => {
|
|
267
284
|
// after a valid response, the stimulus will have the CSS class 'responded'
|
|
268
285
|
// which can be used to provide visual feedback that a response was recorded
|
|
269
286
|
display_element.querySelector("#jspsych-video-keyboard-response-stimulus").className +=
|
|
@@ -292,11 +309,69 @@ class VideoKeyboardResponsePlugin implements JsPsychPlugin<Info> {
|
|
|
292
309
|
|
|
293
310
|
// end trial if time limit is set
|
|
294
311
|
if (trial.trial_duration !== null) {
|
|
295
|
-
this.jsPsych.pluginAPI.setTimeout(
|
|
296
|
-
|
|
297
|
-
|
|
312
|
+
this.jsPsych.pluginAPI.setTimeout(end_trial, trial.trial_duration);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
simulate(
|
|
317
|
+
trial: TrialType<Info>,
|
|
318
|
+
simulation_mode,
|
|
319
|
+
simulation_options: any,
|
|
320
|
+
load_callback: () => void
|
|
321
|
+
) {
|
|
322
|
+
if (simulation_mode == "data-only") {
|
|
323
|
+
load_callback();
|
|
324
|
+
this.simulate_data_only(trial, simulation_options);
|
|
325
|
+
}
|
|
326
|
+
if (simulation_mode == "visual") {
|
|
327
|
+
this.simulate_visual(trial, simulation_options, load_callback);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private simulate_data_only(trial: TrialType<Info>, simulation_options) {
|
|
332
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
333
|
+
|
|
334
|
+
this.jsPsych.finishTrial(data);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
private simulate_visual(trial: TrialType<Info>, simulation_options, load_callback: () => void) {
|
|
338
|
+
const data = this.create_simulation_data(trial, simulation_options);
|
|
339
|
+
|
|
340
|
+
const display_element = this.jsPsych.getDisplayElement();
|
|
341
|
+
|
|
342
|
+
this.trial(display_element, trial);
|
|
343
|
+
load_callback();
|
|
344
|
+
|
|
345
|
+
const video_element = display_element.querySelector<HTMLVideoElement>(
|
|
346
|
+
"#jspsych-video-button-response-stimulus"
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const respond = () => {
|
|
350
|
+
if (data.rt !== null) {
|
|
351
|
+
this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
if (!trial.response_allowed_while_playing) {
|
|
356
|
+
video_element.addEventListener("ended", respond);
|
|
357
|
+
} else {
|
|
358
|
+
respond();
|
|
298
359
|
}
|
|
299
360
|
}
|
|
361
|
+
|
|
362
|
+
private create_simulation_data(trial: TrialType<Info>, simulation_options) {
|
|
363
|
+
const default_data = {
|
|
364
|
+
stimulus: trial.stimulus,
|
|
365
|
+
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
|
|
366
|
+
response: this.jsPsych.pluginAPI.getValidKey(trial.choices),
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
|
|
370
|
+
|
|
371
|
+
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
|
|
372
|
+
|
|
373
|
+
return data;
|
|
374
|
+
}
|
|
300
375
|
}
|
|
301
376
|
|
|
302
377
|
export default VideoKeyboardResponsePlugin;
|