From b02e73ea21ab17498478e4c12cf5fcfa19456e61 Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Mon, 26 Oct 2020 16:58:45 -0700 Subject: [PATCH 1/6] warn about .mov files - #649 --- docs/plugins/jspsych-video-button-response.md | 2 +- docs/plugins/jspsych-video-keyboard-response.md | 2 +- docs/plugins/jspsych-video-slider-response.md | 2 +- plugins/jspsych-video-button-response.js | 3 +++ plugins/jspsych-video-keyboard-response.js | 3 +++ plugins/jspsych-video-slider-response.js | 3 +++ 6 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/plugins/jspsych-video-button-response.md b/docs/plugins/jspsych-video-button-response.md index 41df3b26..5c2a5d7a 100644 --- a/docs/plugins/jspsych-video-button-response.md +++ b/docs/plugins/jspsych-video-button-response.md @@ -8,7 +8,7 @@ Parameters with a default value of *undefined* must be specified. Other paramete Parameter | Type | Default Value | Description ----------|------|---------------|------------ -sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference. +sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The plugin does not reliably support .mov files. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference. choices | array of strings | [] | Labels for the buttons. Each different string in the array will generate a different button. button_html | HTML string | `''` | A template of HTML for generating the button elements. You can override this to create customized buttons of various kinds. The string `%choice%` will be changed to the corresponding element of the `choices` array. You may also specify an array of strings, if you need different HTML to render for each button. If you do specify an array, the `choices` array and this array must have the same length. The HTML from position 0 in the `button_html` array will be used to create the button for element 0 in the `choices` array, and so on. margin_vertical | string | '0px' | Vertical margin of the button(s). diff --git a/docs/plugins/jspsych-video-keyboard-response.md b/docs/plugins/jspsych-video-keyboard-response.md index 6173a408..eaf727e8 100644 --- a/docs/plugins/jspsych-video-keyboard-response.md +++ b/docs/plugins/jspsych-video-keyboard-response.md @@ -8,7 +8,7 @@ Parameters with a default value of *undefined* must be specified. Other paramete Parameter | Type | Default Value | Description ----------|------|---------------|------------ -sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference. +sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The plugin does not reliably support .mov files. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference. prompt | string | null | This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention is that it can be used to provide a reminder about the action the subject is supposed to take (e.g., which key to press). width | numeric | width of the video file | The width of the video display in pixels. height | numeric | heigh of the video file | The height of the video display in pixels. diff --git a/docs/plugins/jspsych-video-slider-response.md b/docs/plugins/jspsych-video-slider-response.md index a43214d3..76dd5cbc 100644 --- a/docs/plugins/jspsych-video-slider-response.md +++ b/docs/plugins/jspsych-video-slider-response.md @@ -8,7 +8,7 @@ Parameters with a default value of *undefined* must be specified. Other paramete Parameter | Type | Default Value | Description ----------|------|---------------|------------ -sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference. +sources | array | *undefined* | An array of file paths to the video. You can specify multiple formats of the same video (e.g., .mp4, .ogg, .webm) to maximize the [cross-browser compatibility](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats). Usually .mp4 is a safe cross-browser option. The plugin does not reliably support .mov files. The player will use the first source file in the array that is compatible with the browser, so specify the files in order of preference. prompt | string | null | This string can contain HTML markup. Any content here will be displayed below the stimulus. The intention is that it can be used to provide a reminder about the action the subject is supposed to take (e.g., which key to press). width | numeric | width of the video file | The width of the video display in pixels. height | numeric | heigh of the video file | The height of the video display in pixels. diff --git a/plugins/jspsych-video-button-response.js b/plugins/jspsych-video-button-response.js index 01944f61..0bc8bc3a 100644 --- a/plugins/jspsych-video-button-response.js +++ b/plugins/jspsych-video-button-response.js @@ -148,6 +148,9 @@ jsPsych.plugins["video-button-response"] = (function() { } var type = file_name.substr(file_name.lastIndexOf('.') + 1); type = type.toLowerCase(); + if (type == "mov") { + console.warn('Warning: video-button-response plugin does not reliably support .mov files.') + } video_html+=''; } } diff --git a/plugins/jspsych-video-keyboard-response.js b/plugins/jspsych-video-keyboard-response.js index 4606be60..a924ac40 100644 --- a/plugins/jspsych-video-keyboard-response.js +++ b/plugins/jspsych-video-keyboard-response.js @@ -129,6 +129,9 @@ jsPsych.plugins["video-keyboard-response"] = (function() { } var type = file_name.substr(file_name.lastIndexOf('.') + 1); type = type.toLowerCase(); + if (type == "mov") { + console.warn('Warning: video-keyboard-response plugin does not reliably support .mov files.') + } video_html+=''; } } diff --git a/plugins/jspsych-video-slider-response.js b/plugins/jspsych-video-slider-response.js index de6c6376..bc261c13 100644 --- a/plugins/jspsych-video-slider-response.js +++ b/plugins/jspsych-video-slider-response.js @@ -171,6 +171,9 @@ jsPsych.plugins["video-slider-response"] = (function() { } var type = file_name.substr(file_name.lastIndexOf('.') + 1); type = type.toLowerCase(); + if (type == "mov") { + console.warn('Warning: video-slider-response plugin does not reliably support .mov files.') + } video_html+=''; } } From 2f421f43170f6edfc358c16259e3de349da922c9 Mon Sep 17 00:00:00 2001 From: bjoluc Date: Mon, 3 Aug 2020 19:56:31 +0200 Subject: [PATCH 2/6] delay `init()` execution until the document is ready This prevents jsPsych from creating a duplicate `body` element when the actual `body` element has not yet been initialized. Fixes #316 --- jspsych.js | 298 +++++++++++++++++++++++++++-------------------------- 1 file changed, 153 insertions(+), 145 deletions(-) diff --git a/jspsych.js b/jspsych.js index ce82e638..7943daa0 100755 --- a/jspsych.js +++ b/jspsych.js @@ -52,158 +52,166 @@ window.jsPsych = (function() { // core.init = function(options) { - - if(typeof options.timeline === 'undefined'){ - console.error('No timeline declared in jsPsych.init. Cannot start experiment.') - } - - if(options.timeline.length == 0){ - console.error('No trials have been added to the timeline (the timeline is an empty array). Cannot start experiment.') - } - - // reset variables - timeline = null; - global_trial_index = 0; - current_trial = {}; - current_trial_finished = false; - paused = false; - waiting = false; - loaded = false; - loadfail = false; - jsPsych.data.reset(); - - var defaults = { - 'display_element': undefined, - 'on_finish': function(data) { - return undefined; - }, - 'on_trial_start': function(trial) { - return undefined; - }, - 'on_trial_finish': function() { - return undefined; - }, - 'on_data_update': function(data) { - return undefined; - }, - 'on_interaction_data_update': function(data){ - return undefined; - }, - 'on_close': function(){ - return undefined; - }, - 'preload_images': [], - 'preload_audio': [], - 'preload_video': [], - 'use_webaudio': true, - 'exclusions': {}, - 'show_progress_bar': false, - 'message_progress_bar': 'Completion Progress', - 'auto_update_progress_bar': true, - 'auto_preload': true, - 'show_preload_progress_bar': true, - 'max_load_time': 60000, - 'max_preload_attempts': 10, - 'default_iti': 0, - 'minimum_valid_rt': 0, - 'experiment_width': null - }; - - // override default options if user specifies an option - opts = Object.assign({}, defaults, options); - - // set DOM element where jsPsych will render content - // if undefined, then jsPsych will use the tag and the entire page - if(typeof opts.display_element == 'undefined'){ - // check if there is a body element on the page - var body = document.querySelector('body'); - if (body === null) { - document.documentElement.appendChild(document.createElement('body')); + function init() { + if(typeof options.timeline === 'undefined'){ + console.error('No timeline declared in jsPsych.init. Cannot start experiment.') } - // using the full page, so we need the HTML element to - // have 100% height, and body to be full width and height with - // no margin - document.querySelector('html').style.height = '100%'; - document.querySelector('body').style.margin = '0px'; - document.querySelector('body').style.height = '100%'; - document.querySelector('body').style.width = '100%'; - opts.display_element = document.querySelector('body'); - } else { - // make sure that the display element exists on the page - var display; - if (opts.display_element instanceof Element) { - var display = opts.display_element; + + if(options.timeline.length == 0){ + console.error('No trials have been added to the timeline (the timeline is an empty array). Cannot start experiment.') + } + + // reset variables + timeline = null; + global_trial_index = 0; + current_trial = {}; + current_trial_finished = false; + paused = false; + waiting = false; + loaded = false; + loadfail = false; + jsPsych.data.reset(); + + var defaults = { + 'display_element': undefined, + 'on_finish': function(data) { + return undefined; + }, + 'on_trial_start': function(trial) { + return undefined; + }, + 'on_trial_finish': function() { + return undefined; + }, + 'on_data_update': function(data) { + return undefined; + }, + 'on_interaction_data_update': function(data){ + return undefined; + }, + 'on_close': function(){ + return undefined; + }, + 'preload_images': [], + 'preload_audio': [], + 'preload_video': [], + 'use_webaudio': true, + 'exclusions': {}, + 'show_progress_bar': false, + 'message_progress_bar': 'Completion Progress', + 'auto_update_progress_bar': true, + 'auto_preload': true, + 'show_preload_progress_bar': true, + 'max_load_time': 60000, + 'max_preload_attempts': 10, + 'default_iti': 0, + 'minimum_valid_rt': 0, + 'experiment_width': null + }; + + // override default options if user specifies an option + opts = Object.assign({}, defaults, options); + + // set DOM element where jsPsych will render content + // if undefined, then jsPsych will use the tag and the entire page + if(typeof opts.display_element == 'undefined'){ + // check if there is a body element on the page + var body = document.querySelector('body'); + if (body === null) { + document.documentElement.appendChild(document.createElement('body')); + } + // using the full page, so we need the HTML element to + // have 100% height, and body to be full width and height with + // no margin + document.querySelector('html').style.height = '100%'; + document.querySelector('body').style.margin = '0px'; + document.querySelector('body').style.height = '100%'; + document.querySelector('body').style.width = '100%'; + opts.display_element = document.querySelector('body'); } else { - var display = document.querySelector('#' + opts.display_element); + // make sure that the display element exists on the page + var display; + if (opts.display_element instanceof Element) { + var display = opts.display_element; + } else { + var display = document.querySelector('#' + opts.display_element); + } + if(display === null) { + console.error('The display_element specified in jsPsych.init() does not exist in the DOM.'); + } else { + opts.display_element = display; + } } - if(display === null) { - console.error('The display_element specified in jsPsych.init() does not exist in the DOM.'); - } else { - opts.display_element = display; - } - } - opts.display_element.innerHTML = '
'; - DOM_container = opts.display_element; - DOM_target = document.querySelector('#jspsych-content'); + opts.display_element.innerHTML = '
'; + DOM_container = opts.display_element; + DOM_target = document.querySelector('#jspsych-content'); - // add tabIndex attribute to scope event listeners - opts.display_element.tabIndex = 0; - - // add CSS class to DOM_target - if(opts.display_element.className.indexOf('jspsych-display-element') == -1){ - opts.display_element.className += ' jspsych-display-element'; - } - DOM_target.className += 'jspsych-content'; - - // set experiment_width if not null - if(opts.experiment_width !== null){ - DOM_target.style.width = opts.experiment_width + "px"; - } - - // create experiment timeline - timeline = new TimelineNode({ - timeline: opts.timeline - }); - - // initialize audio context based on options and browser capabilities - jsPsych.pluginAPI.initAudio(); - - // below code resets event listeners that may have lingered from - // a previous incomplete experiment loaded in same DOM. - jsPsych.pluginAPI.reset(opts.display_element); - // create keyboard event listeners - jsPsych.pluginAPI.createKeyboardEventListeners(opts.display_element); - // create listeners for user browser interaction - jsPsych.data.createInteractionListeners(); - - // add event for closing window - window.addEventListener('beforeunload', opts.on_close); - - // check exclusions before continuing - checkExclusions(opts.exclusions, - function(){ - // success! user can continue... - // start experiment, with or without preloading - if(opts.auto_preload){ - jsPsych.pluginAPI.autoPreload(timeline, startExperiment, opts.preload_images, opts.preload_audio, opts.preload_video, opts.show_preload_progress_bar); - if(opts.max_load_time > 0){ - setTimeout(function(){ - if(!loaded && !loadfail){ - core.loadFail(); - } - }, opts.max_load_time); - } - } else { - startExperiment(); - } - }, - function(){ - // fail. incompatible user. + // add tabIndex attribute to scope event listeners + opts.display_element.tabIndex = 0; + // add CSS class to DOM_target + if(opts.display_element.className.indexOf('jspsych-display-element') == -1){ + opts.display_element.className += ' jspsych-display-element'; } - ); - }; + DOM_target.className += 'jspsych-content'; + + // set experiment_width if not null + if(opts.experiment_width !== null){ + DOM_target.style.width = opts.experiment_width + "px"; + } + + // create experiment timeline + timeline = new TimelineNode({ + timeline: opts.timeline + }); + + // initialize audio context based on options and browser capabilities + jsPsych.pluginAPI.initAudio(); + + // below code resets event listeners that may have lingered from + // a previous incomplete experiment loaded in same DOM. + jsPsych.pluginAPI.reset(opts.display_element); + // create keyboard event listeners + jsPsych.pluginAPI.createKeyboardEventListeners(opts.display_element); + // create listeners for user browser interaction + jsPsych.data.createInteractionListeners(); + + // add event for closing window + window.addEventListener('beforeunload', opts.on_close); + + // check exclusions before continuing + checkExclusions(opts.exclusions, + function(){ + // success! user can continue... + // start experiment, with or without preloading + if(opts.auto_preload){ + jsPsych.pluginAPI.autoPreload(timeline, startExperiment, opts.preload_images, opts.preload_audio, opts.preload_video, opts.show_preload_progress_bar); + if(opts.max_load_time > 0){ + setTimeout(function(){ + if(!loaded && !loadfail){ + core.loadFail(); + } + }, opts.max_load_time); + } + } else { + startExperiment(); + } + }, + function(){ + // fail. incompatible user. + + } + ); + }; + + // execute init() when the document is ready + if (document.readyState === "complete") { + init(); + } else { + window.addEventListener("load", init); + } + } core.progress = function() { From ffb9c5d39c40737c338400b3f6a32b3cf007d6e1 Mon Sep 17 00:00:00 2001 From: bjoluc Date: Wed, 28 Oct 2020 12:15:04 +0100 Subject: [PATCH 3/6] add tests for delayed `init()` execution --- tests/jsPsych/init.test.js | 48 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 tests/jsPsych/init.test.js diff --git a/tests/jsPsych/init.test.js b/tests/jsPsych/init.test.js new file mode 100644 index 00000000..6f3ea688 --- /dev/null +++ b/tests/jsPsych/init.test.js @@ -0,0 +1,48 @@ +require("../../jspsych"); +require("../../plugins/jspsych-html-keyboard-response"); + +describe("jsPsych init", () => { + beforeEach(() => { + document.body.innerHTML = ""; + }); + + function setReadyState(targetState) { + jest + .spyOn(document, "readyState", "get") + .mockImplementation(() => targetState); + } + + function getBodyHTML() { + return document.body.innerHTML; + } + + function init() { + jsPsych.init({ + timeline: [ + { + type: "html-keyboard-response", + stimulus: "foo", + }, + ], + }); + } + + it("should delay execution until the document is ready", () => { + expect(getBodyHTML()).toBe(""); + + setReadyState("loading"); + init(); + expect(getBodyHTML()).toBe(""); + + // Simulate the document getting ready + setReadyState("complete"); + window.dispatchEvent(new Event("load")); + expect(getBodyHTML()).not.toBe(""); + }); + + it("should execute immediately when the document is ready", () => { + // The document is ready by default in jsdom + init(); + expect(getBodyHTML()).not.toBe(""); + }); +}); From 71ce1263f929ec04fb9527eab74968e39681a31d Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Thu, 29 Oct 2020 15:55:49 -0700 Subject: [PATCH 4/6] put button :hover in media query, add button :active style - #977 --- css/jspsych.css | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/css/jspsych.css b/css/jspsych.css index 1e53897a..20d10905 100644 --- a/css/jspsych.css +++ b/css/jspsych.css @@ -76,9 +76,17 @@ border-color: #ccc; } -.jspsych-btn:hover { +/* only apply the hover style on devices with a mouse/pointer that can hover - issue #977 */ +@media (hover: hover) { + .jspsych-btn:hover { + background-color: #ddd; + border-color: #aaa; + } +} + +.jspsych-btn:active { background-color: #ddd; - border-color: #aaa; + border-color:#000000; } .jspsych-btn:disabled { From a2b0cecfbb67e2fc0dc896329913c8f0c8f65b86 Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Fri, 30 Oct 2020 12:01:41 -0400 Subject: [PATCH 5/6] fixes Preloading progress bar not centered when experiment_width property is set in jsPsych.init #1160 --- jspsych.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jspsych.js b/jspsych.js index 7943daa0..3605af65 100755 --- a/jspsych.js +++ b/jspsych.js @@ -2608,7 +2608,7 @@ jsPsych.pluginAPI = (function() { var loaded = 0; if(progress_bar){ - var pb_html = "
"; + var pb_html = "
"; pb_html += "
"; pb_html += "
"; jsPsych.getDisplayElement().innerHTML = pb_html; From 879f6a7cb562cc9d5de276d26dceb376245a302d Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Sat, 31 Oct 2020 14:39:32 -0700 Subject: [PATCH 6/6] fix problem with non-zero video start time in FF --- examples/jspsych-video-button-response.html | 1 - examples/jspsych-video-keyboard-response.html | 4 +- plugins/jspsych-video-button-response.js | 46 +++++++++++++------ plugins/jspsych-video-keyboard-response.js | 44 ++++++++++++------ plugins/jspsych-video-slider-response.js | 30 +++++++++--- 5 files changed, 88 insertions(+), 37 deletions(-) diff --git a/examples/jspsych-video-button-response.html b/examples/jspsych-video-button-response.html index b62378b5..6b120228 100644 --- a/examples/jspsych-video-button-response.html +++ b/examples/jspsych-video-button-response.html @@ -31,7 +31,6 @@ //stop: 9, rate: 1.5, //trial_duration: 2000, - response_ends_trial: true } diff --git a/examples/jspsych-video-keyboard-response.html b/examples/jspsych-video-keyboard-response.html index e5ee8f27..7d8d3bea 100644 --- a/examples/jspsych-video-keyboard-response.html +++ b/examples/jspsych-video-keyboard-response.html @@ -24,8 +24,8 @@ //height: 600, autoplay: true, //controls: true, - start: 8.75, - stop: 9, + //start: 8, + //stop: 9, rate: 1.5, //trial_duration: 2000, //trial_ends_after_video: true, diff --git a/plugins/jspsych-video-button-response.js b/plugins/jspsych-video-button-response.js index 0bc8bc3a..26cfed34 100644 --- a/plugins/jspsych-video-button-response.js +++ b/plugins/jspsych-video-button-response.js @@ -131,12 +131,19 @@ jsPsych.plugins["video-button-response"] = (function() { if(trial.height) { video_html += ' height="'+trial.height+'"'; } - if(trial.autoplay){ + if(trial.autoplay & (trial.start == null)){ + // if autoplay is true and the start time is specified, then the video will start automatically + // via the play() method, rather than the autoplay attribute, to prevent showing the first frame video_html += " autoplay "; } if(trial.controls){ video_html +=" controls "; } + if (trial.start !== null) { + // hide video element when page loads if the start time is specified, + // to prevent the video element from showing the first frame + video_html += ' style="visibility: hidden;"'; + } video_html +=">"; var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]); @@ -186,30 +193,41 @@ jsPsych.plugins["video-button-response"] = (function() { var start_time = performance.now(); + var video_element = display_element.querySelector('#jspsych-video-button-response-stimulus'); + if(video_preload_blob){ - display_element.querySelector('#jspsych-video-button-response-stimulus').src = video_preload_blob; + video_element.src = video_preload_blob; } - display_element.querySelector('#jspsych-video-button-response-stimulus').onended = function(){ - if(trial.trial_ends_after_video){ - end_trial(); + video_element.playbackRate = trial.rate; + + // if video start time is specified, hide the video and set the starting time + // before showing and playing, so that the video doesn't automatically show the first frame + if(trial.start !== null){ + video_element.pause(); + video_element.currentTime = trial.start; + video_element.onseeked = function() { + video_element.style.visibility = "visible"; + if (trial.autoplay) { + video_element.play(); + } } } - if(trial.start !== null){ - display_element.querySelector('#jspsych-video-button-response-stimulus').currentTime = trial.start; - } - if(trial.stop !== null){ - display_element.querySelector('#jspsych-video-button-response-stimulus').addEventListener('timeupdate', function(e){ - var currenttime = display_element.querySelector('#jspsych-video-button-response-stimulus').currentTime; + video_element.addEventListener('timeupdate', function(e){ + var currenttime = video_element.currentTime; if(currenttime >= trial.stop){ - display_element.querySelector('#jspsych-video-button-response-stimulus').pause(); + video_element.pause(); } }) } - display_element.querySelector('#jspsych-video-button-response-stimulus').playbackRate = trial.rate; + video_element.onended = function(){ + if(trial.trial_ends_after_video){ + end_trial(); + } + } // add event listeners to buttons for (var i = 0; i < trial.choices.length; i++) { @@ -256,7 +274,7 @@ jsPsych.plugins["video-button-response"] = (function() { // after a valid response, the stimulus will have the CSS class 'responded' // which can be used to provide visual feedback that a response was recorded - display_element.querySelector('#jspsych-video-button-response-stimulus').className += ' responded'; + video_element.className += ' responded'; // disable all the buttons after a response var btns = document.querySelectorAll('.jspsych-video-button-response-button button'); diff --git a/plugins/jspsych-video-keyboard-response.js b/plugins/jspsych-video-keyboard-response.js index a924ac40..2296282d 100644 --- a/plugins/jspsych-video-keyboard-response.js +++ b/plugins/jspsych-video-keyboard-response.js @@ -112,12 +112,19 @@ jsPsych.plugins["video-keyboard-response"] = (function() { if(trial.height) { video_html += ' height="'+trial.height+'"'; } - if(trial.autoplay){ + if(trial.autoplay & (trial.start == null)){ + // if autoplay is true and the start time is specified, then the video will start automatically + // via the play() method, rather than the autoplay attribute, to prevent showing the first frame video_html += " autoplay "; } if(trial.controls){ video_html +=" controls "; } + if (trial.start !== null) { + // hide video element when page loads if the start time is specified, + // to prevent the video element from showing the first frame + video_html += ' style="visibility: hidden;"'; + } video_html +=">"; var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]); @@ -145,30 +152,41 @@ jsPsych.plugins["video-keyboard-response"] = (function() { display_element.innerHTML = video_html; + var video_element = display_element.querySelector('#jspsych-video-keyboard-response-stimulus'); + if(video_preload_blob){ - display_element.querySelector('#jspsych-video-keyboard-response-stimulus').src = video_preload_blob; + video_element.src = video_preload_blob; } - display_element.querySelector('#jspsych-video-keyboard-response-stimulus').onended = function(){ - if(trial.trial_ends_after_video){ - end_trial(); + video_element.playbackRate = trial.rate; + + // if video start time is specified, hide the video and set the starting time + // before showing and playing, so that the video doesn't automatically show the first frame + if(trial.start !== null){ + video_element.pause(); + video_element.currentTime = trial.start; + video_element.onseeked = function() { + video_element.style.visibility = "visible"; + if (trial.autoplay) { + video_element.play(); + } } } - if(trial.start !== null){ - display_element.querySelector('#jspsych-video-keyboard-response-stimulus').currentTime = trial.start; - } - if(trial.stop !== null){ - display_element.querySelector('#jspsych-video-keyboard-response-stimulus').addEventListener('timeupdate', function(e){ - var currenttime = display_element.querySelector('#jspsych-video-keyboard-response-stimulus').currentTime; + video_element.addEventListener('timeupdate', function(e){ + var currenttime = video_element.currentTime; if(currenttime >= trial.stop){ - display_element.querySelector('#jspsych-video-keyboard-response-stimulus').pause(); + video_element.pause(); } }) } - display_element.querySelector('#jspsych-video-keyboard-response-stimulus').playbackRate = trial.rate; + video_element.onended = function(){ + if(trial.trial_ends_after_video){ + end_trial(); + } + } // store response var response = { diff --git a/plugins/jspsych-video-slider-response.js b/plugins/jspsych-video-slider-response.js index bc261c13..48e6d233 100644 --- a/plugins/jspsych-video-slider-response.js +++ b/plugins/jspsych-video-slider-response.js @@ -154,12 +154,19 @@ jsPsych.plugins["video-slider-response"] = (function() { if(trial.height) { video_html += ' height="'+trial.height+'"'; } - if(trial.autoplay){ + if(trial.autoplay & (trial.start == null)){ + // if autoplay is true and the start time is specified, then the video will start automatically + // via the play() method, rather than the autoplay attribute, to prevent showing the first frame video_html += " autoplay "; } if(trial.controls){ video_html +=" controls "; } + if (trial.start !== null) { + // hide video element when page loads if the start time is specified, + // to prevent the video element from showing the first frame + video_html += ' style="visibility: hidden;"'; + } video_html +=">"; var video_preload_blob = jsPsych.pluginAPI.getVideoBuffer(trial.sources[0]); @@ -215,14 +222,19 @@ jsPsych.plugins["video-slider-response"] = (function() { video_element.src = video_preload_blob; } - video_element.onended = function(){ - if(trial.trial_ends_after_video){ - end_trial(); - } - } + video_element.playbackRate = trial.rate; + // if video start time is specified, hide the video and set the starting time + // before showing and playing, so that the video doesn't automatically show the first frame if(trial.start !== null){ + video_element.pause(); video_element.currentTime = trial.start; + video_element.onseeked = function() { + video_element.style.visibility = "visible"; + if (trial.autoplay) { + video_element.play(); + } + } } if(trial.stop !== null){ @@ -234,7 +246,11 @@ jsPsych.plugins["video-slider-response"] = (function() { }) } - video_element.playbackRate = trial.rate; + video_element.onended = function(){ + if(trial.trial_ends_after_video){ + end_trial(); + } + } if(trial.require_movement){ display_element.querySelector('#jspsych-video-slider-response-response').addEventListener('click', function(){