@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.
@@ -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 = function () {
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 = function () { };
192
+ video_element.onseeked = () => { };
186
193
  };
187
- video_element.onplaying = function () {
194
+ video_element.onplaying = () => {
188
195
  video_element.currentTime = trial.start;
189
- video_element.onplaying = function () { };
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", function (e) {
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 = function () { };
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 = function (info) {
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(function () {
266
- end_trial();
267
- }, trial.trial_duration);
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":"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"}
@@ -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 r=this.jsPsych.pluginAPI.getVideoBuffer(t.stimulus[0]);if(!r)for(var a=0;a<t.stimulus.length;a++){var l=t.stimulus[a];l.indexOf("?")>-1&&(l=l.substring(0,l.indexOf("?")));var i=l.substr(l.lastIndexOf(".")+1);"mov"==(i=i.toLowerCase())&&console.warn("Warning: video-keyboard-response plugin does not reliably support .mov files."),s+='<source src="'+l+'" type="video/'+i+'">'}s+="</video>",s+="</div>",null!==t.prompt&&(s+=t.prompt),e.innerHTML=s;var n=e.querySelector("#jspsych-video-keyboard-response-stimulus");r&&(n.src=r),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=function(){n.style.visibility="visible",n.muted=!1,t.autoplay?n.play():n.pause(),n.onseeked=function(){}},n.onplaying=function(){n.currentTime=t.start,n.onplaying=function(){}},n.muted=!0,n.play());let o=!1;null!==t.stop&&n.addEventListener("timeupdate",(function(e){n.currentTime>=t.stop&&(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=function(){};var s={rt:p.rt,stimulus:t.stimulus,response:p.key};e.innerHTML="",this.jsPsych.finishTrial(s)};var y=function(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((function(){u()}),t.trial_duration)}}return s.info=t,s}(jsPsychModule);
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 = function () {
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 = function () { };
193
+ video_element.onseeked = () => { };
187
194
  };
188
- video_element.onplaying = function () {
195
+ video_element.onplaying = () => {
189
196
  video_element.currentTime = trial.start;
190
- video_element.onplaying = function () { };
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", function (e) {
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 = function () { };
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 = function (info) {
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(function () {
267
- end_trial();
268
- }, trial.trial_duration);
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;
@@ -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 = function () {
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 = function () { };
191
+ video_element.onseeked = () => { };
185
192
  };
186
- video_element.onplaying = function () {
193
+ video_element.onplaying = () => {
187
194
  video_element.currentTime = trial.start;
188
- video_element.onplaying = function () { };
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", function (e) {
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 = function () { };
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 = function (info) {
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(function () {
265
- end_trial();
266
- }, trial.trial_duration);
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.0.0",
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.0.0"
37
+ "jspsych": ">=7.1.0"
38
38
  },
39
39
  "devDependencies": {
40
- "@jspsych/config": "^1.0.0",
41
- "@jspsych/test-utils": "^1.0.0"
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 = function () {
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 = function () {};
208
+ video_element.onseeked = () => {};
201
209
  };
202
- video_element.onplaying = function () {
210
+ video_element.onplaying = () => {
203
211
  video_element.currentTime = trial.start;
204
- video_element.onplaying = function () {};
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", function (e) {
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 = function () {};
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 = function (info) {
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(function () {
296
- end_trial();
297
- }, trial.trial_duration);
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;