@jspsych/plugin-video-keyboard-response 1.1.3 → 2.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.
@@ -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 // 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
+ {"version":3,"file":"index.browser.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-video-keyboard-response\",\n \"version\": \"2.1.0\",\n \"description\": \"jsPsych plugin for playing a video file and getting a keyboard response\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest --passWithNoTests\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-video-keyboard-response\"\n },\n \"author\": \"Josh de Leeuw\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/video-keyboard-response\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.0\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"video-keyboard-response\",\n version: version,\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 data: {\n /** Indicates which key the participant pressed. */\n response: {\n type: ParameterType.STRING,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the\n * stimulus first appears on the screen until the participant's response.\n * */\n rt: {\n type: ParameterType.INT,\n },\n /** The `stimulus` array. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n stimulus: {\n type: ParameterType.STRING,\n array: true,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\n/**\n * This plugin plays a video file and records a keyboard response. The stimulus can be displayed until a response is\n * given, or for a pre-determined amount of time. The trial can be ended automatically when the participant responds,\n * when the video file has finished playing, or if the participant has failed to respond within a fixed length of time.\n * You can also prevent a keyboard response from being recorded before the video has finished playing.\n *\n * Video files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if you are\n * using timeline variables or another dynamic method to specify the video stimulus, you will need to\n * [manually preload](../overview/media-preloading.md#manual-preloading) the videos. Also note that video preloading\n * is disabled when the experiment is running as a file (i.e. opened directly in the browser, rather than through a\n * server), in order to prevent CORS errors - see the section on [Running Experiments](../overview/running-experiments.md)\n * for more information.\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/latest/plugins/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 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 // 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":";;;EAEE,IAAW,OAAA,GAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IC8GA,SAAA,EAAA;EAAA;;KAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,2 +1,5 @@
1
- var jsPsychVideoKeyboardResponse=function(e){"use strict";function t(e,t){for(var s=0;s<t.length;s++){var r=t[s];r.enumerable=r.enumerable||!1,r.configurable=!0,"value"in r&&(r.writable=!0),Object.defineProperty(e,(a=r.key,i=void 0,"symbol"==typeof(i=function(e,t){if("object"!=typeof e||null===e)return e;var s=e[Symbol.toPrimitive];if(void 0!==s){var r=s.call(e,t||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(a,"string"))?i:String(i)),r)}var a,i}var s={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}}},r=function(){function e(t){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.jsPsych=t}var s,r,a;return s=e,r=[{key:"trial",value:function(e,t){var s=this;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 r="<div>";r+='<video id="jspsych-video-keyboard-response-stimulus"',t.width&&(r+=' width="'+t.width+'"'),t.height&&(r+=' height="'+t.height+'"'),t.autoplay&&null==t.start&&(r+=" autoplay "),t.controls&&(r+=" controls "),null!==t.start&&(r+=' style="visibility: hidden;"'),r+=">";var a=this.jsPsych.pluginAPI.getVideoBuffer(t.stimulus[0]);if(!a)for(var i=0;i<t.stimulus.length;i++){var n=t.stimulus[i];n.indexOf("?")>-1&&(n=n.substring(0,n.indexOf("?")));var l=n.substr(n.lastIndexOf(".")+1);"mov"==(l=l.toLowerCase())&&console.warn("Warning: video-keyboard-response plugin does not reliably support .mov files."),r+='<source src="'+n+'" type="video/'+l+'">'}r+="</video>",r+="</div>",null!==t.prompt&&(r+=t.prompt),e.innerHTML=r;var o=e.querySelector("#jspsych-video-keyboard-response-stimulus");a&&(o.src=a),o.onended=function(){t.trial_ends_after_video&&y(),0!=t.response_allowed_while_playing||t.trial_ends_after_video||s.jsPsych.pluginAPI.getKeyboardResponse({callback_function:d,valid_responses:t.choices,rt_method:"performance",persist:!1,allow_held_key:!1})},o.playbackRate=t.rate,null!==t.start&&(o.pause(),o.onseeked=function(){o.style.visibility="visible",o.muted=!1,t.autoplay?o.play():o.pause(),o.onseeked=function(){}},o.onplaying=function(){o.currentTime=t.start,o.onplaying=function(){}},o.muted=!0,o.play());var u=!1;null!==t.stop&&o.addEventListener("timeupdate",(function(e){o.currentTime>=t.stop&&(t.response_allowed_while_playing||s.jsPsych.pluginAPI.getKeyboardResponse({callback_function:d,valid_responses:t.choices,rt_method:"performance",persist:!1,allow_held_key:!1}),o.pause(),t.trial_ends_after_video&&!u&&(u=!0,y()))}));var p={rt:null,key:null},y=function(){s.jsPsych.pluginAPI.clearAllTimeouts(),s.jsPsych.pluginAPI.cancelAllKeyboardResponses(),e.querySelector("#jspsych-video-keyboard-response-stimulus").pause(),e.querySelector("#jspsych-video-keyboard-response-stimulus").onended=function(){};var r={rt:p.rt,stimulus:t.stimulus,response:p.key};e.innerHTML="",s.jsPsych.finishTrial(r)},d=function(s){e.querySelector("#jspsych-video-keyboard-response-stimulus").className+=" responded",null==p.key&&(p=s),t.response_ends_trial&&y()};"NO_KEYS"!=t.choices&&t.response_allowed_while_playing&&this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:d,valid_responses:t.choices,rt_method:"performance",persist:!1,allow_held_key:!1}),null!==t.trial_duration&&this.jsPsych.pluginAPI.setTimeout(y,t.trial_duration)}},{key:"simulate",value:function(e,t,s,r){"data-only"==t&&(r(),this.simulate_data_only(e,s)),"visual"==t&&this.simulate_visual(e,s,r)}},{key:"simulate_data_only",value:function(e,t){var s=this.create_simulation_data(e,t);this.jsPsych.finishTrial(s)}},{key:"simulate_visual",value:function(e,t,s){var r=this,a=this.create_simulation_data(e,t),i=this.jsPsych.getDisplayElement();this.trial(i,e),s();var n=i.querySelector("#jspsych-video-button-response-stimulus"),l=function(){null!==a.rt&&r.jsPsych.pluginAPI.pressKey(a.response,a.rt)};e.response_allowed_while_playing?l():n.addEventListener("ended",l)}},{key:"create_simulation_data",value:function(e,t){var s={stimulus:e.stimulus,rt:this.jsPsych.randomization.sampleExGaussian(500,50,1/150,!0),response:this.jsPsych.pluginAPI.getValidKey(e.choices)},r=this.jsPsych.pluginAPI.mergeSimulationData(s,t);return this.jsPsych.pluginAPI.ensureSimulationDataConsistency(e,r),r}}],r&&t(s.prototype,r),a&&t(s,a),Object.defineProperty(s,"prototype",{writable:!1}),e}();return r.info=s,r}(jsPsychModule);
2
- //# sourceMappingURL=https://unpkg.com/@jspsych/plugin-video-keyboard-response@1.1.3/dist/index.browser.min.js.map
1
+ var jsPsychVideoKeyboardResponse=function(r){"use strict";var c="2.1.0";const _={name:"video-keyboard-response",version:c,parameters:{stimulus:{type:r.ParameterType.VIDEO,pretty_name:"Video",default:void 0,array:!0},choices:{type:r.ParameterType.KEYS,pretty_name:"Choices",default:"ALL_KEYS"},prompt:{type:r.ParameterType.HTML_STRING,pretty_name:"Prompt",default:null},width:{type:r.ParameterType.INT,pretty_name:"Width",default:""},height:{type:r.ParameterType.INT,pretty_name:"Height",default:""},autoplay:{type:r.ParameterType.BOOL,pretty_name:"Autoplay",default:!0},controls:{type:r.ParameterType.BOOL,pretty_name:"Controls",default:!1},start:{type:r.ParameterType.FLOAT,pretty_name:"Start",default:null},stop:{type:r.ParameterType.FLOAT,pretty_name:"Stop",default:null},rate:{type:r.ParameterType.FLOAT,pretty_name:"Rate",default:1},trial_ends_after_video:{type:r.ParameterType.BOOL,pretty_name:"End trial after video finishes",default:!1},trial_duration:{type:r.ParameterType.INT,pretty_name:"Trial duration",default:null},response_ends_trial:{type:r.ParameterType.BOOL,pretty_name:"Response ends trial",default:!0},response_allowed_while_playing:{type:r.ParameterType.BOOL,pretty_name:"Response allowed while playing",default:!0}},data:{response:{type:r.ParameterType.STRING},rt:{type:r.ParameterType.INT},stimulus:{type:r.ParameterType.STRING,array:!0}},citations:{apa:"de Leeuw, J. R., Gilbert, R. A., & Luchterhandt, B. (2023). jsPsych: Enabling an Open-Source Collaborative Ecosystem of Behavioral Experiments. Journal of Open Source Software, 8(85), 5351. https://doi.org/10.21105/joss.05351 ",bibtex:'@article{Leeuw2023jsPsych, author = {de Leeuw, Joshua R. and Gilbert, Rebecca A. and Luchterhandt, Bj{\\" o}rn}, journal = {Journal of Open Source Software}, doi = {10.21105/joss.05351}, issn = {2475-9066}, number = {85}, year = {2023}, month = {may 11}, pages = {5351}, publisher = {Open Journals}, title = {jsPsych: Enabling an {Open}-{Source} {Collaborative} {Ecosystem} of {Behavioral} {Experiments}}, url = {https://joss.theoj.org/papers/10.21105/joss.05351}, volume = {8}, } '}};class m{constructor(s){this.jsPsych=s}trial(s,e){if(!Array.isArray(e.stimulus))throw new Error(`
2
+ The stimulus property for the video-keyboard-response plugin must be an array
3
+ of files. See https://www.jspsych.org/latest/plugins/video-keyboard-response/#parameters
4
+ `);var t="<div>";t+='<video id="jspsych-video-keyboard-response-stimulus"',e.width&&(t+=' width="'+e.width+'"'),e.height&&(t+=' height="'+e.height+'"'),e.autoplay&&e.start==null&&(t+=" autoplay "),e.controls&&(t+=" controls "),e.start!==null&&(t+=' style="visibility: hidden;"'),t+=">";var i=this.jsPsych.pluginAPI.getVideoBuffer(e.stimulus[0]);if(!i)for(var n=0;n<e.stimulus.length;n++){var o=e.stimulus[n];o.indexOf("?")>-1&&(o=o.substring(0,o.indexOf("?")));var l=o.substr(o.lastIndexOf(".")+1);l=l.toLowerCase(),l=="mov"&&console.warn("Warning: video-keyboard-response plugin does not reliably support .mov files."),t+='<source src="'+o+'" type="video/'+l+'">'}t+="</video>",t+="</div>",e.prompt!==null&&(t+=e.prompt),s.innerHTML=t;var a=s.querySelector("#jspsych-video-keyboard-response-stimulus");i&&(a.src=i),a.onended=()=>{if(e.trial_ends_after_video&&d(),e.response_allowed_while_playing==!1&&!e.trial_ends_after_video)var p=this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:y,valid_responses:e.choices,rt_method:"performance",persist:!1,allow_held_key:!1})},a.playbackRate=e.rate,e.start!==null&&(a.pause(),a.onseeked=()=>{a.style.visibility="visible",a.muted=!1,e.autoplay?a.play():a.pause(),a.onseeked=()=>{}},a.onplaying=()=>{a.currentTime=e.start,a.onplaying=()=>{}},a.muted=!0,a.play());let h=!1;e.stop!==null&&a.addEventListener("timeupdate",p=>{var v=a.currentTime;if(v>=e.stop){if(!e.response_allowed_while_playing)var g=this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:y,valid_responses:e.choices,rt_method:"performance",persist:!1,allow_held_key:!1});a.pause(),e.trial_ends_after_video&&!h&&(h=!0,d())}});var u={rt:null,key:null};const d=()=>{this.jsPsych.pluginAPI.cancelAllKeyboardResponses(),s.querySelector("#jspsych-video-keyboard-response-stimulus").pause(),s.querySelector("#jspsych-video-keyboard-response-stimulus").onended=()=>{};var p={rt:u.rt,stimulus:e.stimulus,response:u.key};this.jsPsych.finishTrial(p)};var y=p=>{s.querySelector("#jspsych-video-keyboard-response-stimulus").className+=" responded",u.key==null&&(u=p),e.response_ends_trial&&d()};if(e.choices!="NO_KEYS"&&e.response_allowed_while_playing)var P=this.jsPsych.pluginAPI.getKeyboardResponse({callback_function:y,valid_responses:e.choices,rt_method:"performance",persist:!1,allow_held_key:!1});e.trial_duration!==null&&this.jsPsych.pluginAPI.setTimeout(d,e.trial_duration)}simulate(s,e,t,i){e=="data-only"&&(i(),this.simulate_data_only(s,t)),e=="visual"&&this.simulate_visual(s,t,i)}simulate_data_only(s,e){const t=this.create_simulation_data(s,e);this.jsPsych.finishTrial(t)}simulate_visual(s,e,t){const i=this.create_simulation_data(s,e),n=this.jsPsych.getDisplayElement();this.trial(n,s),t();const o=n.querySelector("#jspsych-video-button-response-stimulus"),l=()=>{i.rt!==null&&this.jsPsych.pluginAPI.pressKey(i.response,i.rt)};s.response_allowed_while_playing?l():o.addEventListener("ended",l)}create_simulation_data(s,e){const t={stimulus:s.stimulus,rt:this.jsPsych.randomization.sampleExGaussian(500,50,.006666666666666667,!0),response:this.jsPsych.pluginAPI.getValidKey(s.choices)},i=this.jsPsych.pluginAPI.mergeSimulationData(t,e);return this.jsPsych.pluginAPI.ensureSimulationDataConsistency(s,i),i}}return m.info=_,m}(jsPsychModule);
5
+ //# sourceMappingURL=https://unpkg.com/@jspsych/plugin-video-keyboard-response@2.1.0/dist/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 // 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","jsPsych","_classCallCheck","this","key","value","display_element","trial","_this","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","clearAllTimeouts","cancelAllKeyboardResponses","trial_data","finishTrial","className","setTimeout","simulation_mode","simulation_options","load_callback","simulate_data_only","simulate_visual","data","create_simulation_data","_this2","getDisplayElement","respond","pressKey","default_data","randomization","sampleExGaussian","getValidKey","mergeSimulationData","ensureSimulationDataConsistency"],"mappings":"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"}
1
+ {"version":3,"file":"index.browser.min.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych/plugin-video-keyboard-response\",\n \"version\": \"2.1.0\",\n \"description\": \"jsPsych plugin for playing a video file and getting a keyboard response\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest --passWithNoTests\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/jspsych/jsPsych.git\",\n \"directory\": \"packages/plugin-video-keyboard-response\"\n },\n \"author\": \"Josh de Leeuw\",\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jsPsych/issues\"\n },\n \"homepage\": \"https://www.jspsych.org/latest/plugins/video-keyboard-response\",\n \"peerDependencies\": {\n \"jspsych\": \">=7.1.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.0\",\n \"@jspsych/test-utils\": \"^1.2.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"video-keyboard-response\",\n version: version,\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 data: {\n /** Indicates which key the participant pressed. */\n response: {\n type: ParameterType.STRING,\n },\n /** The response time in milliseconds for the participant to make a response. The time is measured from when the\n * stimulus first appears on the screen until the participant's response.\n * */\n rt: {\n type: ParameterType.INT,\n },\n /** The `stimulus` array. This will be encoded as a JSON string when data is saved using the `.json()` or `.csv()` functions. */\n stimulus: {\n type: ParameterType.STRING,\n array: true,\n },\n },\n // prettier-ignore\n citations: '__CITATIONS__',\n};\n\ntype Info = typeof info;\n\n/**\n * This plugin plays a video file and records a keyboard response. The stimulus can be displayed until a response is\n * given, or for a pre-determined amount of time. The trial can be ended automatically when the participant responds,\n * when the video file has finished playing, or if the participant has failed to respond within a fixed length of time.\n * You can also prevent a keyboard response from being recorded before the video has finished playing.\n *\n * Video files can be automatically preloaded by jsPsych using the [`preload` plugin](preload.md). However, if you are\n * using timeline variables or another dynamic method to specify the video stimulus, you will need to\n * [manually preload](../overview/media-preloading.md#manual-preloading) the videos. Also note that video preloading\n * is disabled when the experiment is running as a file (i.e. opened directly in the browser, rather than through a\n * server), in order to prevent CORS errors - see the section on [Running Experiments](../overview/running-experiments.md)\n * for more information.\n *\n * @author Josh de Leeuw\n * @see {@link https://www.jspsych.org/latest/plugins/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 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 // 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":["version"],"mappings":"0DAEEA,IAAAA,EAAW,kxCC8GA,UAAA,iuBAAe;;;"}