From 3a08b15fc9bd41362b9810d1d038cdfa591d9f34 Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Wed, 6 Jan 2021 18:54:28 -0500 Subject: [PATCH 01/26] start implementing plugin, didn't get far yet --- plugins/jspsych-preload.js | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 plugins/jspsych-preload.js diff --git a/plugins/jspsych-preload.js b/plugins/jspsych-preload.js new file mode 100644 index 00000000..beab7af6 --- /dev/null +++ b/plugins/jspsych-preload.js @@ -0,0 +1,56 @@ +/** + * jspsych-preload + * documentation: docs.jspsych.org + * + **/ + +jsPsych.plugins['preload'] = (function() { + + var plugin = {}; + + plugin.info = { + name: 'preload', + description: '', + parameters: { + auto_preload: { + type: jsPsych.plugins.parameterType.BOOL, + default: false + }, + images: { + type: jsPsych.plugins.parameterType.STRING, + default: [] + }, + audio: { + type: jsPsych.plugins.parameterType.STRING, + default: [] + }, + video: { + type: jsPsych.plugins.parameterType.STRING, + default: [] + }, + show_progress_bar: { + type: jsPsych.plugins.parameterType.BOOL, + default: true, + }, + show_detailed_errors: { + type: jsPsych.plugins.parameterType.BOOL, + default: false + }, + } + } + + plugin.trial = function(display_element, trial) { + + + function end_trial(){ + var trial_data = { + value: return_val + }; + + jsPsych.finishTrial(trial_data); + } + }; + + return plugin; + })(); + \ No newline at end of file From d4830275d5ed21746c7e6148b57219b3de1650ac Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Thu, 7 Jan 2021 09:19:16 -0500 Subject: [PATCH 02/26] working on shifting preload stuff --- jspsych.js | 101 ++++++++++++++++++++----------------- plugins/jspsych-preload.js | 4 ++ 2 files changed, 59 insertions(+), 46 deletions(-) diff --git a/jspsych.js b/jspsych.js index 0d845605..b1957285 100755 --- a/jspsych.js +++ b/jspsych.js @@ -2501,56 +2501,56 @@ jsPsych.pluginAPI = (function() { }; - module.preloadVideo = function(video, callback_complete, callback_load) { + module.preloadVideo = function(video, callback_complete, callback_load) { - // flatten the images array - video = jsPsych.utils.flatten(video); - video = jsPsych.utils.unique(video); + // flatten the images array + video = jsPsych.utils.flatten(video); + video = jsPsych.utils.unique(video); - var n_loaded = 0; - var loadfn = !callback_load ? function() {} : callback_load; - var finishfn = !callback_complete ? function() {} : callback_complete; + var n_loaded = 0; + var loadfn = !callback_load ? function() {} : callback_load; + var finishfn = !callback_complete ? function() {} : callback_complete; - if(video.length===0){ - finishfn(); - return; - } + if(video.length===0){ + finishfn(); + return; + } - function preload_video(source, count){ - count = count || 1; - //based on option 4 here: http://dinbror.dk/blog/how-to-preload-entire-html5-video-before-play-solved/ - var request = new XMLHttpRequest(); - request.open('GET', source, true); - request.responseType = 'blob'; - request.onload = function() { - if (this.status === 200 || this.status === 0) { - var videoBlob = this.response; - video_buffers[source] = URL.createObjectURL(videoBlob); // IE10+ - n_loaded++; - loadfn(n_loaded); - if (n_loaded === video.length) { - finishfn(); - } - } - }; + function preload_video(source, count){ + count = count || 1; + //based on option 4 here: http://dinbror.dk/blog/how-to-preload-entire-html5-video-before-play-solved/ + var request = new XMLHttpRequest(); + request.open('GET', source, true); + request.responseType = 'blob'; + request.onload = function() { + if (this.status === 200 || this.status === 0) { + var videoBlob = this.response; + video_buffers[source] = URL.createObjectURL(videoBlob); // IE10+ + n_loaded++; + loadfn(n_loaded); + if (n_loaded === video.length) { + finishfn(); + } + } + }; - request.onerror = function(){ - if(count < jsPsych.initSettings().max_preload_attempts){ - setTimeout(function(){ - preload_video(source, count+1) - }, 200); - } else { - jsPsych.loadFail(); - } - } - request.send(); - } + request.onerror = function(){ + if(count < jsPsych.initSettings().max_preload_attempts){ + setTimeout(function(){ + preload_video(source, count+1) + }, 200); + } else { + jsPsych.loadFail(); + } + } + request.send(); + } - for (var i = 0; i < video.length; i++) { - preload_video(video[i]); - } + for (var i = 0; i < video.length; i++) { + preload_video(video[i]); + } - }; + }; module.registerPreload = function(plugin_name, parameter, media_type, conditional_function) { if (['audio', 'image', 'video'].indexOf(media_type)===-1) { @@ -2567,7 +2567,7 @@ jsPsych.pluginAPI = (function() { preloads.push(preload); } - module.autoPreload = function(timeline, callback, file_protocol, images, audio, video, progress_bar) { + module.getAutoPreloadList = function(timeline){ // list of items to preload images = images || []; audio = audio || []; @@ -2606,11 +2606,20 @@ jsPsych.pluginAPI = (function() { // remove any nulls false values images = images.filter(function(x) { return x != false && x != null}) audio = audio.filter(function(x) { return x != false && x != null}) + video = video.filter(function(x) { return x != false && x != null}) + + return { + images, audio, video + } + } + + module.autoPreload = function(timeline, callback, file_protocol, images, audio, video, progress_bar) { + + var {images, audio, video} = module.getAutoPreloadList(timeline); + // prevent all video preloading (auto and manual) when file is opened directly in browser if (file_protocol === true) { video = []; - } else { - video = video.filter(function(x) { return x != false && x != null}) } var total_n = images.length + audio.length + video.length; diff --git a/plugins/jspsych-preload.js b/plugins/jspsych-preload.js index beab7af6..c18d6e95 100644 --- a/plugins/jspsych-preload.js +++ b/plugins/jspsych-preload.js @@ -40,6 +40,10 @@ jsPsych.plugins['preload'] = (function() { } plugin.trial = function(display_element, trial) { + + if(trial.auto_preload){ + var {images, audio, video} = jsPsych.getAutoPreloadList() //timeline arg? + } function end_trial(){ From ba1e7a72c9662dad3e852fa54f08063f52b71970 Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Thu, 7 Jan 2021 10:05:47 -0500 Subject: [PATCH 03/26] core of preload plugin --- jspsych.js | 109 ++++++++++++++----------------------- plugins/jspsych-preload.js | 97 ++++++++++++++++++++++++++++++--- 2 files changed, 130 insertions(+), 76 deletions(-) diff --git a/jspsych.js b/jspsych.js index b1957285..7cf6d37e 100755 --- a/jspsych.js +++ b/jspsych.js @@ -1084,7 +1084,8 @@ jsPsych.plugins = (function() { AUDIO: 9, VIDEO: 10, OBJECT: 11, - COMPLEX: 12 + COMPLEX: 12, + TIMELINE: 13 } module.universalPluginParameters = { @@ -2354,7 +2355,7 @@ jsPsych.pluginAPI = (function() { var img_cache = {}; - module.preloadAudioFiles = function(files, callback_complete, callback_load) { + module.preloadAudioFiles = function(files, callback_complete, callback_load, callback_error) { files = jsPsych.utils.flatten(files); files = jsPsych.utils.unique(files); @@ -2362,6 +2363,7 @@ jsPsych.pluginAPI = (function() { var n_loaded = 0; var loadfn = (typeof callback_load === 'undefined') ? function() {} : callback_load; var finishfn = (typeof callback_complete === 'undefined') ? function() {} : callback_complete; + var errorfn = (typeof callback_error === 'undefined') ? function() {} : callback_error; if(files.length==0){ finishfn(); @@ -2381,18 +2383,12 @@ jsPsych.pluginAPI = (function() { if(n_loaded == files.length) { finishfn(); } - }, function() { - console.error('Error loading audio file: ' + bufferID); + }, function(e) { + errorfn(e); }); } - request.onerror = function(){ - if(count < jsPsych.initSettings().max_preload_attempts){ - setTimeout(function(){ - load_audio_file_webaudio(source, count+1) - }, 200); - } else { - jsPsych.loadFail(); - } + request.onerror = function(e){ + errorfn(e); } request.send(); } @@ -2409,24 +2405,12 @@ jsPsych.pluginAPI = (function() { } audio.removeEventListener('canplaythrough', handleCanPlayThrough); }); - audio.addEventListener('error', function handleError(){ - if(count < jsPsych.initSettings().max_preload_attempts){ - setTimeout(function(){ - load_audio_file_html5audio(source, count+1) - }, 200); - } else { - jsPsych.loadFail(); - } + audio.addEventListener('error', function handleError(e){ + errorfn(e); audio.removeEventListener('error', handleError); }); - audio.addEventListener('abort', function handleAbort(){ - if(count < jsPsych.initSettings().max_preload_attempts){ - setTimeout(function(){ - load_audio_file_html5audio(source, count+1) - }, 200); - } else { - jsPsych.loadFail(); - } + audio.addEventListener('abort', function handleAbort(e){ + errorfn(e); audio.removeEventListener('abort', handleAbort); }); audio.src = source; @@ -2452,7 +2436,7 @@ jsPsych.pluginAPI = (function() { } - module.preloadImages = function(images, callback_complete, callback_load) { + module.preloadImages = function(images, callback_complete, callback_load, callback_error) { // flatten the images array images = jsPsych.utils.flatten(images); @@ -2461,15 +2445,14 @@ jsPsych.pluginAPI = (function() { var n_loaded = 0; var loadfn = (typeof callback_load === 'undefined') ? function() {} : callback_load; var finishfn = (typeof callback_complete === 'undefined') ? function() {} : callback_complete; + var errorfn = (typeof callback_error === 'undefined') ? function() {} : callback_error; if(images.length === 0){ finishfn(); return; } - function preload_image(source, count){ - count = count || 1; - + function preload_image(source){ var img = new Image(); img.onload = function() { @@ -2480,14 +2463,8 @@ jsPsych.pluginAPI = (function() { } }; - img.onerror = function() { - if(count < jsPsych.initSettings().max_preload_attempts){ - setTimeout(function(){ - preload_image(source, count+1); - }, 200); - } else { - jsPsych.loadFail(); - } + img.onerror = function(e) { + errorfn(e); } img.src = source; @@ -2501,7 +2478,7 @@ jsPsych.pluginAPI = (function() { }; - module.preloadVideo = function(video, callback_complete, callback_load) { + module.preloadVideo = function(video, callback_complete, callback_load, callback_error) { // flatten the images array video = jsPsych.utils.flatten(video); @@ -2510,6 +2487,7 @@ jsPsych.pluginAPI = (function() { var n_loaded = 0; var loadfn = !callback_load ? function() {} : callback_load; var finishfn = !callback_complete ? function() {} : callback_complete; + var errorfn = (typeof callback_error === 'undefined') ? function() {} : callback_error; if(video.length===0){ finishfn(); @@ -2517,37 +2495,31 @@ jsPsych.pluginAPI = (function() { } function preload_video(source, count){ - count = count || 1; - //based on option 4 here: http://dinbror.dk/blog/how-to-preload-entire-html5-video-before-play-solved/ - var request = new XMLHttpRequest(); - request.open('GET', source, true); - request.responseType = 'blob'; - request.onload = function() { - if (this.status === 200 || this.status === 0) { - var videoBlob = this.response; - video_buffers[source] = URL.createObjectURL(videoBlob); // IE10+ - n_loaded++; - loadfn(n_loaded); - if (n_loaded === video.length) { - finishfn(); - } - } - }; - - request.onerror = function(){ - if(count < jsPsych.initSettings().max_preload_attempts){ - setTimeout(function(){ - preload_video(source, count+1) - }, 200); - } else { - jsPsych.loadFail(); - } + count = count || 1; + //based on option 4 here: http://dinbror.dk/blog/how-to-preload-entire-html5-video-before-play-solved/ + var request = new XMLHttpRequest(); + request.open('GET', source, true); + request.responseType = 'blob'; + request.onload = function() { + if (this.status === 200 || this.status === 0) { + var videoBlob = this.response; + video_buffers[source] = URL.createObjectURL(videoBlob); // IE10+ + n_loaded++; + loadfn(n_loaded); + if (n_loaded === video.length) { + finishfn(); + } } - request.send(); + }; + + request.onerror = function(e){ + errorfn(); + } + request.send(); } for (var i = 0; i < video.length; i++) { - preload_video(video[i]); + preload_video(video[i]); } }; @@ -2568,6 +2540,7 @@ jsPsych.pluginAPI = (function() { } module.getAutoPreloadList = function(timeline){ + // list of items to preload images = images || []; audio = audio || []; diff --git a/plugins/jspsych-preload.js b/plugins/jspsych-preload.js index c18d6e95..613ef38e 100644 --- a/plugins/jspsych-preload.js +++ b/plugins/jspsych-preload.js @@ -1,7 +1,6 @@ /** * jspsych-preload * documentation: docs.jspsych.org - * **/ jsPsych.plugins['preload'] = (function() { @@ -13,8 +12,8 @@ jsPsych.plugins['preload'] = (function() { description: '', parameters: { auto_preload: { - type: jsPsych.plugins.parameterType.BOOL, - default: false + type: jsPsych.plugins.parameterType.TIMELINE, + default: [] }, images: { type: jsPsych.plugins.parameterType.STRING, @@ -28,6 +27,10 @@ jsPsych.plugins['preload'] = (function() { type: jsPsych.plugins.parameterType.STRING, default: [] }, + message: { + type: jsPsych.plugins.parameterType.HTML_STRING, + default: null + }, show_progress_bar: { type: jsPsych.plugins.parameterType.BOOL, default: true, @@ -36,19 +39,97 @@ jsPsych.plugins['preload'] = (function() { type: jsPsych.plugins.parameterType.BOOL, default: false }, + max_load_time: { + type: jsPsych.plugins.parameterType.INT, + default: null + } } } plugin.trial = function(display_element, trial) { - if(trial.auto_preload){ - var {images, audio, video} = jsPsych.getAutoPreloadList() //timeline arg? + // create list of media to preload //// + + var images = []; + var audio = []; + var video = []; + + if(trial.auto_preload.length > 0){ + var auto_preloads = jsPsych.getAutoPreloadList(trial.auto_preload); + images = images.concat(auto_preloads.images); + audio = audio.concat(auto_preloads.audio); + video = video.concat(auto_preloads.video); } - - + + images = images.concat(trial.images); + audio = audio.concat(trial.audio); + video = video.concat(trial.video); + + // render display of message and progress bar + + var html = ''; + + if(trial.message !== null){ + html += trial.message; + } + + if(trial.show_progress_bar){ + html += ` +
+
+
`; + } + + display_element.innerHTML = html; + + // do preloading + + if(trial.max_load_time !== null){ + jsPsych.pluginAPI.setTimeout(end_trial, trial.max_load_time); + } + + var total_n = images.length + audio.length + video.length; + var loaded = 0; + + function update_loading_progress_bar(){ + loaded++; + if(trial.show_progress_bar){ + var percent_loaded = (loaded/total_n)*100; + var preload_progress_bar = jsPsych.getDisplayElement().querySelector('#jspsych-loading-progress-bar'); + if (preload_progress_bar !== null) { + preload_progress_bar.style.width = percent_loaded+"%"; + } + } + } + + function loading_error(e){ + console.log(e); + } + + function load_video(cb){ + jsPsych.preloadVideo(video, cb(), update_loading_progress_bar, loading_error); + } + + function load_audio(cb){ + jsPsych.preloadAudio(video, cb(), update_loading_progress_bar, loading_error); + } + + function load_images(cb){ + jsPsych.preloadImages(video, cb(), update_loading_progress_bar, loading_error); + } + + load_video( + load_audio( + load_images( + end_trial + ) + ) + ) + function end_trial(){ + jsPsych.pluginAPI.clearAllTimeouts(); + var trial_data = { - value: return_val }; jsPsych.finishTrial(trial_data); From e892d58e2e75d09fb2e35ce5cbfb81a7d163b5f2 Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Thu, 7 Jan 2021 15:54:05 -0500 Subject: [PATCH 04/26] remove preloading from jsPsych.init --- jspsych.js | 77 ++------------------------------------ plugins/jspsych-preload.js | 8 ++-- 2 files changed, 8 insertions(+), 77 deletions(-) diff --git a/jspsych.js b/jspsych.js index 7cf6d37e..ab30a3e8 100755 --- a/jspsych.js +++ b/jspsych.js @@ -95,18 +95,11 @@ window.jsPsych = (function() { '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, + 'auto_update_progress_bar': true, 'default_iti': 0, 'minimum_valid_rt': 0, 'experiment_width': null, @@ -198,23 +191,11 @@ window.jsPsych = (function() { checkExclusions(opts.exclusions, function(){ // success! user can continue... - // start experiment, with or without preloading - if(opts.auto_preload){ - jsPsych.pluginAPI.autoPreload(timeline, startExperiment, file_protocol, 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(); - } + // start experiment + startExperiment(); }, function(){ // fail. incompatible user. - } ); }; @@ -334,15 +315,8 @@ window.jsPsych = (function() { } } - core.addNodeToEndOfTimeline = function(new_timeline, preload_callback){ + core.addNodeToEndOfTimeline = function(new_timeline){ timeline.insert(new_timeline); - if(typeof preload_callback !== 'undefined'){ - if(opts.auto_preload){ - jsPsych.pluginAPI.autoPreload(timeline, preload_callback, file_protocol); - } else { - preload_callback(); - } - } } core.pauseExperiment = function(){ @@ -2586,49 +2560,6 @@ jsPsych.pluginAPI = (function() { } } - module.autoPreload = function(timeline, callback, file_protocol, images, audio, video, progress_bar) { - - var {images, audio, video} = module.getAutoPreloadList(timeline); - - // prevent all video preloading (auto and manual) when file is opened directly in browser - if (file_protocol === true) { - video = []; - } - - var total_n = images.length + audio.length + video.length; - - var loaded = 0; - - if(progress_bar){ - var pb_html = "
"; - pb_html += "
"; - pb_html += "
"; - jsPsych.getDisplayElement().innerHTML = pb_html; - } - - function update_loading_progress_bar(){ - loaded++; - if(progress_bar){ - var percent_loaded = (loaded/total_n)*100; - var preload_progress_bar = jsPsych.getDisplayElement().querySelector('#jspsych-loading-progress-bar'); - if (preload_progress_bar !== null) { - preload_progress_bar.style.width = percent_loaded+"%"; - } - } - } - - // do the preloading - // first the images, then when the images are complete - // wait for the audio files to finish - module.preloadImages(images, function() { - module.preloadAudioFiles(audio, function() { - module.preloadVideo(video, function() { - callback(); - }, update_loading_progress_bar); - }, update_loading_progress_bar); - }, update_loading_progress_bar); - } - /** * Allows communication with user hardware through our custom Google Chrome extension + native C++ program * @param {object} mess The message to be passed to our extension, see its documentation for the expected members of this object. diff --git a/plugins/jspsych-preload.js b/plugins/jspsych-preload.js index 613ef38e..d5790c61 100644 --- a/plugins/jspsych-preload.js +++ b/plugins/jspsych-preload.js @@ -55,7 +55,7 @@ jsPsych.plugins['preload'] = (function() { var video = []; if(trial.auto_preload.length > 0){ - var auto_preloads = jsPsych.getAutoPreloadList(trial.auto_preload); + var auto_preloads = jsPsych.pluginAPI.getAutoPreloadList(trial.auto_preload); images = images.concat(auto_preloads.images); audio = audio.concat(auto_preloads.audio); video = video.concat(auto_preloads.video); @@ -107,15 +107,15 @@ jsPsych.plugins['preload'] = (function() { } function load_video(cb){ - jsPsych.preloadVideo(video, cb(), update_loading_progress_bar, loading_error); + jsPsych.pluginAPI.preloadVideo(video, cb(), update_loading_progress_bar, loading_error); } function load_audio(cb){ - jsPsych.preloadAudio(video, cb(), update_loading_progress_bar, loading_error); + jsPsych.pluginAPI.preloadAudio(video, cb(), update_loading_progress_bar, loading_error); } function load_images(cb){ - jsPsych.preloadImages(video, cb(), update_loading_progress_bar, loading_error); + jsPsych.pluginAPI.preloadImages(video, cb(), update_loading_progress_bar, loading_error); } load_video( From 6135ed47d57f2b993a3712d8016e0a2a4e561fbe Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Thu, 7 Jan 2021 16:06:48 -0500 Subject: [PATCH 05/26] remove the conditional_function option from registerPreload --- jspsych.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/jspsych.js b/jspsych.js index ab30a3e8..2d89c86e 100755 --- a/jspsych.js +++ b/jspsych.js @@ -2498,7 +2498,7 @@ jsPsych.pluginAPI = (function() { }; - module.registerPreload = function(plugin_name, parameter, media_type, conditional_function) { + module.registerPreload = function(plugin_name, parameter, media_type) { if (['audio', 'image', 'video'].indexOf(media_type)===-1) { console.error('Invalid media_type parameter for jsPsych.pluginAPI.registerPreload. Please check the plugin file.'); } @@ -2506,26 +2506,24 @@ jsPsych.pluginAPI = (function() { var preload = { plugin: plugin_name, parameter: parameter, - media_type: media_type, - conditional_function: conditional_function + media_type: media_type } preloads.push(preload); } - module.getAutoPreloadList = function(timeline){ + module.getAutoPreloadList = function(timeline_description){ // list of items to preload - images = images || []; - audio = audio || []; - video = video || []; + var images = []; + var audio = []; + var video = []; // construct list for (var i = 0; i < preloads.length; i++) { var type = preloads[i].plugin; var param = preloads[i].parameter; var media = preloads[i].media_type; - var func = preloads[i].conditional_function; var trials = timeline.trialsOfType(type); for (var j = 0; j < trials.length; j++) { From 3f1abdec88f3fe74873938325e5b6342ff21d17b Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Thu, 7 Jan 2021 16:13:26 -0500 Subject: [PATCH 06/26] adjust getAutoPreloadList to work with timeline description --- jspsych.js | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/jspsych.js b/jspsych.js index 2d89c86e..02865935 100755 --- a/jspsych.js +++ b/jspsych.js @@ -2514,6 +2514,22 @@ jsPsych.pluginAPI = (function() { module.getAutoPreloadList = function(timeline_description){ + function getTrialsOfTypeFromTimelineDescription(td, type){ + var trials = []; + + for(var i=0; i Date: Thu, 7 Jan 2021 16:13:34 -0500 Subject: [PATCH 07/26] add working test --- tests/jsPsych.pluginAPI/preloads.test.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 tests/jsPsych.pluginAPI/preloads.test.js diff --git a/tests/jsPsych.pluginAPI/preloads.test.js b/tests/jsPsych.pluginAPI/preloads.test.js new file mode 100644 index 00000000..86cd46e4 --- /dev/null +++ b/tests/jsPsych.pluginAPI/preloads.test.js @@ -0,0 +1,24 @@ +const root = '../../'; +const utils = require('../testing-utils.js'); + +beforeEach(function(){ + require(root + 'jspsych.js'); + require(root + 'plugins/jspsych-html-keyboard-response.js'); +}); + +describe('getAutoPreloadList', function(){ + test('works with images', function(){ + require(root + 'plugins/jspsych-image-keyboard-response.js'); + + var t = { + type: 'image-keyboard-response', + stimulus: 'img/foo.png' + } + + var timeline = [t]; + + var images = jsPsych.pluginAPI.getAutoPreloadList(timeline).images; + + expect(images[0]).toBe('img/foo.png'); + }) +}) \ No newline at end of file From df4057bd52af89c72bdfcf786ba07f36b939625e Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Thu, 7 Jan 2021 16:15:13 -0500 Subject: [PATCH 08/26] remove outdated test --- tests/jsPsych/timelines.test.js | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/tests/jsPsych/timelines.test.js b/tests/jsPsych/timelines.test.js index 92bddb57..be099a1f 100644 --- a/tests/jsPsych/timelines.test.js +++ b/tests/jsPsych/timelines.test.js @@ -468,31 +468,5 @@ describe('add node to end of timeline', function(){ utils.pressKey(32); }); - test('adds node to end of timeline, with callback', function() { - var t = { - type: 'html-keyboard-response', - stimulus: 'foo', - on_finish: function(){ - jsPsych.pauseExperiment(); - jsPsych.addNodeToEndOfTimeline({ - timeline: [{ - type: 'html-keyboard-response', - stimulus: 'bar' - }] - }, jsPsych.resumeExperiment) - } - }; - - jsPsych.init({ - timeline: [t] - }); - - expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo'); - utils.pressKey(32); - expect(jsPsych.getDisplayElement().innerHTML).toMatch('bar'); - utils.pressKey(32); - - }); - }); From d5d462d4c8b8014908b209733a6ebba0f43bd99a Mon Sep 17 00:00:00 2001 From: Josh de Leeuw Date: Thu, 7 Jan 2021 22:15:38 -0500 Subject: [PATCH 09/26] switch auto_preload to bool, add trials parameter, implement working tests --- jspsych.js | 5 ++- plugins/jspsych-preload.js | 43 +++++++++++++++--------- tests/jsPsych.pluginAPI/preloads.test.js | 19 +++++++++++ tests/plugins/plugin-preload.test.js | 40 ++++++++++++++++++++++ 4 files changed, 91 insertions(+), 16 deletions(-) create mode 100644 tests/plugins/plugin-preload.test.js diff --git a/jspsych.js b/jspsych.js index 02865935..7b457c44 100755 --- a/jspsych.js +++ b/jspsych.js @@ -2530,6 +2530,10 @@ jsPsych.pluginAPI = (function() { return trials; } + if(typeof timeline_description == 'undefined'){ + timeline_description = jsPsych.initSettings().timeline; + } + // list of items to preload var images = []; var audio = []; @@ -2541,7 +2545,6 @@ jsPsych.pluginAPI = (function() { var param = preloads[i].parameter; var media = preloads[i].media_type; - var trials = getTrialsOfTypeFromTimelineDescription(timeline_description, type); for (var j = 0; j < trials.length; j++) { diff --git a/plugins/jspsych-preload.js b/plugins/jspsych-preload.js index d5790c61..2f54f74d 100644 --- a/plugins/jspsych-preload.js +++ b/plugins/jspsych-preload.js @@ -12,6 +12,10 @@ jsPsych.plugins['preload'] = (function() { description: '', parameters: { auto_preload: { + type: jsPsych.plugins.parameterType.BOOL, + default: false + }, + trials: { type: jsPsych.plugins.parameterType.TIMELINE, default: [] }, @@ -54,17 +58,28 @@ jsPsych.plugins['preload'] = (function() { var audio = []; var video = []; - if(trial.auto_preload.length > 0){ - var auto_preloads = jsPsych.pluginAPI.getAutoPreloadList(trial.auto_preload); - images = images.concat(auto_preloads.images); - audio = audio.concat(auto_preloads.audio); - video = video.concat(auto_preloads.video); + if(trial.auto_preload){ + var auto_preload = jsPsych.pluginAPI.getAutoPreloadList(); + images = images.concat(auto_preload.images); + audio = audio.concat(auto_preload.audio); + video = video.concat(auto_preload.video); + } + + if(trial.trials.length > 0){ + var trial_preloads = jsPsych.pluginAPI.getAutoPreloadList(trial.trials); + images = images.concat(trial_preloads.images); + audio = audio.concat(trial_preloads.audio); + video = video.concat(trial_preloads.video); } images = images.concat(trial.images); audio = audio.concat(trial.audio); video = video.concat(trial.video); + images = jsPsych.utils.unique(jsPsych.utils.flatten(images)) + audio = jsPsych.utils.unique(jsPsych.utils.flatten(audio)) + video = jsPsych.utils.unique(jsPsych.utils.flatten(video)) + // render display of message and progress bar var html = ''; @@ -107,24 +122,22 @@ jsPsych.plugins['preload'] = (function() { } function load_video(cb){ - jsPsych.pluginAPI.preloadVideo(video, cb(), update_loading_progress_bar, loading_error); + jsPsych.pluginAPI.preloadVideo(video, cb, update_loading_progress_bar, loading_error); } function load_audio(cb){ - jsPsych.pluginAPI.preloadAudio(video, cb(), update_loading_progress_bar, loading_error); + jsPsych.pluginAPI.preloadAudio(audio, cb, update_loading_progress_bar, loading_error); } function load_images(cb){ - jsPsych.pluginAPI.preloadImages(video, cb(), update_loading_progress_bar, loading_error); + jsPsych.pluginAPI.preloadImages(images, cb, update_loading_progress_bar, loading_error); } - load_video( - load_audio( - load_images( - end_trial - ) - ) - ) + load_video(function(){ + load_audio(function(){ + load_images(end_trial) + }) + }); function end_trial(){ jsPsych.pluginAPI.clearAllTimeouts(); diff --git a/tests/jsPsych.pluginAPI/preloads.test.js b/tests/jsPsych.pluginAPI/preloads.test.js index 86cd46e4..2ad211e7 100644 --- a/tests/jsPsych.pluginAPI/preloads.test.js +++ b/tests/jsPsych.pluginAPI/preloads.test.js @@ -7,6 +7,25 @@ beforeEach(function(){ }); describe('getAutoPreloadList', function(){ + test('gets whole timeline when no argument provided', function(){ + require(root + 'plugins/jspsych-image-keyboard-response.js'); + + var t = { + type: 'image-keyboard-response', + stimulus: 'img/foo.png', + render_on_canvas: false + } + + var timeline = [t]; + + jsPsych.init({ + timeline: timeline + }) + + var images = jsPsych.pluginAPI.getAutoPreloadList().images; + + expect(images[0]).toBe('img/foo.png'); + }) test('works with images', function(){ require(root + 'plugins/jspsych-image-keyboard-response.js'); diff --git a/tests/plugins/plugin-preload.test.js b/tests/plugins/plugin-preload.test.js new file mode 100644 index 00000000..20af5d78 --- /dev/null +++ b/tests/plugins/plugin-preload.test.js @@ -0,0 +1,40 @@ +const root = '../../'; + +describe('preload plugin', function () { + + beforeEach(function () { + require(root + 'jspsych.js'); + require(root + 'plugins/jspsych-preload.js'); + }); + + test('loads correctly', function () { + expect(typeof window.jsPsych.plugins['preload']).not.toBe('undefined'); + }); + + test('auto_preload method works with simple timeline', function () { + + require(root + 'plugins/jspsych-image-keyboard-response.js'); + + jsPsych.pluginAPI.preloadImages = jest.fn((x, cb) => { cb(); }); + jsPsych.pluginAPI.preloadAudio = jest.fn((x, cb) => { cb(); }); + jsPsych.pluginAPI.preloadVideo = jest.fn((x, cb) => { cb(); }); + + var preload = { + type: 'preload', + auto_preload: true + } + + var trial = { + type: 'image-keyboard-response', + stimulus: 'img/foo.png', + render_on_canvas: false + } + + jsPsych.init({ + timeline: [preload, trial] + }); + + expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toStrictEqual(['img/foo.png']); + }); + +}); From e26fbab2956184f5cea91a16cbb8ed6df6e3688c Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Tue, 19 Jan 2021 16:51:16 -0800 Subject: [PATCH 10/26] change jsPsych.pluginAPI.preloadAudioFiles to preloadAudio (for consistency with images and video) #1351 --- jspsych.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jspsych.js b/jspsych.js index 7b457c44..e75691bf 100755 --- a/jspsych.js +++ b/jspsych.js @@ -2329,7 +2329,7 @@ jsPsych.pluginAPI = (function() { var img_cache = {}; - module.preloadAudioFiles = function(files, callback_complete, callback_load, callback_error) { + module.preloadAudio = function(files, callback_complete, callback_load, callback_error) { files = jsPsych.utils.flatten(files); files = jsPsych.utils.unique(files); From 661df3e07830aa2077546fd7233ac964dec55cbc Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Tue, 19 Jan 2021 16:57:34 -0800 Subject: [PATCH 11/26] add success and timeout flags to data, record failed files, clear display, separate functions for timeout and success #1351 --- plugins/jspsych-preload.js | 44 ++++++++++++++++++++++++++++++++------ 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/plugins/jspsych-preload.js b/plugins/jspsych-preload.js index 2f54f74d..03584237 100644 --- a/plugins/jspsych-preload.js +++ b/plugins/jspsych-preload.js @@ -52,7 +52,13 @@ jsPsych.plugins['preload'] = (function() { plugin.trial = function(display_element, trial) { - // create list of media to preload //// + var success = false; + var timeout = false; + var failed_images = []; + var failed_audio = []; + var failed_video = []; + + // create list of media to preload // var images = []; var audio = []; @@ -100,7 +106,7 @@ jsPsych.plugins['preload'] = (function() { // do preloading if(trial.max_load_time !== null){ - jsPsych.pluginAPI.setTimeout(end_trial, trial.max_load_time); + jsPsych.pluginAPI.setTimeout(on_timeout, trial.max_load_time); } var total_n = images.length + audio.length + video.length; @@ -118,7 +124,13 @@ jsPsych.plugins['preload'] = (function() { } function loading_error(e){ - console.log(e); + if (e.path[0].localName == "img") { + failed_images.push(e.path[0].currentSrc); + } else if (e.path[0].localName == "audio") { + failed_audio.push(e.path[0].currentSrc); + } else if (e.path[0].localName == "video") { + failed_video.push(e.path[0].currentSrc); + } } function load_video(cb){ @@ -135,16 +147,36 @@ jsPsych.plugins['preload'] = (function() { load_video(function(){ load_audio(function(){ - load_images(end_trial) + load_images(on_success) }) }); - function end_trial(){ + function on_success() { + // clear timeout immediately after finishing, to handle race condition with max_load_time jsPsych.pluginAPI.clearAllTimeouts(); + success = true; + end_trial(); + } + function on_timeout() { + if (typeof success !== 'undefined' && success === false) { + timeout = true; + end_trial(); + } + } + + function end_trial(){ + // clear timeout again when end_trial is called, to handle race condition with max_load_time + jsPsych.pluginAPI.clearAllTimeouts(); var trial_data = { + success: success, + timeout: timeout, + failed_images: failed_images, + failed_audio: failed_audio, + failed_video: failed_video }; - + // clear the display + display_element.innerHTML = ''; jsPsych.finishTrial(trial_data); } }; From 44b5b478e5ed62c93c3491b76bf7a53ae8a51f7f Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Tue, 19 Jan 2021 17:00:19 -0800 Subject: [PATCH 12/26] add preload example file --- examples/jspsych-preload.html | 119 ++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 examples/jspsych-preload.html diff --git a/examples/jspsych-preload.html b/examples/jspsych-preload.html new file mode 100644 index 00000000..4fe1ea88 --- /dev/null +++ b/examples/jspsych-preload.html @@ -0,0 +1,119 @@ + + + + + + + + + + + + + From 692b3cbfa2e7c3878049982240cdab8231fa010a Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Wed, 27 Jan 2021 16:28:24 -0800 Subject: [PATCH 13/26] change preload callback functions so that they return file name and error, rather than load count #1351 --- jspsych.js | 44 ++++++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/jspsych.js b/jspsych.js index e75691bf..d840021e 100755 --- a/jspsych.js +++ b/jspsych.js @@ -337,6 +337,10 @@ window.jsPsych = (function() { DOM_target.innerHTML = message; } + core.getSafeModeStatus = function() { + return file_protocol; + } + function TimelineNode(parameters, parent, relativeID) { // a unique ID for this node, relative to the parent @@ -2353,17 +2357,24 @@ jsPsych.pluginAPI = (function() { context.decodeAudioData(request.response, function(buffer) { audio_buffers[source] = buffer; n_loaded++; - loadfn(n_loaded); + loadfn(source); if(n_loaded == files.length) { finishfn(); } }, function(e) { - errorfn(e); + errorfn({source: source, error: e}); }); } request.onerror = function(e){ - errorfn(e); + errorfn({source: source, error: e}); } + // don't think this works - need to fix or remove + // request.onloadend = function(e) { + // if(request.status == 404) { + // console.log('jspsych.js audio 404 ',e); + // errorfn({source: source, error: '404'}); + // } + // } request.send(); } @@ -2373,18 +2384,18 @@ jsPsych.pluginAPI = (function() { audio.addEventListener('canplaythrough', function handleCanPlayThrough(){ audio_buffers[source] = audio; n_loaded++; - loadfn(n_loaded); + loadfn(source); if(n_loaded == files.length){ finishfn(); } audio.removeEventListener('canplaythrough', handleCanPlayThrough); }); audio.addEventListener('error', function handleError(e){ - errorfn(e); + errorfn({source: audio.src, error: e}); audio.removeEventListener('error', handleError); }); audio.addEventListener('abort', function handleAbort(e){ - errorfn(e); + errorfn({source: audio.src, error: e}); audio.removeEventListener('abort', handleAbort); }); audio.src = source; @@ -2394,7 +2405,7 @@ jsPsych.pluginAPI = (function() { var bufferID = files[i]; if (typeof audio_buffers[bufferID] !== 'undefined') { n_loaded++; - loadfn(n_loaded); + loadfn(bufferID); if(n_loaded == files.length) { finishfn(); } @@ -2417,8 +2428,8 @@ jsPsych.pluginAPI = (function() { images = jsPsych.utils.unique(images); var n_loaded = 0; - var loadfn = (typeof callback_load === 'undefined') ? function() {} : callback_load; var finishfn = (typeof callback_complete === 'undefined') ? function() {} : callback_complete; + var loadfn = (typeof callback_load === 'undefined') ? function() {} : callback_load; var errorfn = (typeof callback_error === 'undefined') ? function() {} : callback_error; if(images.length === 0){ @@ -2431,14 +2442,14 @@ jsPsych.pluginAPI = (function() { img.onload = function() { n_loaded++; - loadfn(n_loaded); + loadfn(img.src); if (n_loaded === images.length) { finishfn(); } }; img.onerror = function(e) { - errorfn(e); + errorfn({source: img.src, error: e}); } img.src = source; @@ -2459,8 +2470,8 @@ jsPsych.pluginAPI = (function() { video = jsPsych.utils.unique(video); var n_loaded = 0; - var loadfn = !callback_load ? function() {} : callback_load; var finishfn = !callback_complete ? function() {} : callback_complete; + var loadfn = !callback_load ? function() {} : callback_load; var errorfn = (typeof callback_error === 'undefined') ? function() {} : callback_error; if(video.length===0){ @@ -2479,16 +2490,21 @@ jsPsych.pluginAPI = (function() { var videoBlob = this.response; video_buffers[source] = URL.createObjectURL(videoBlob); // IE10+ n_loaded++; - loadfn(n_loaded); + loadfn(source); if (n_loaded === video.length) { finishfn(); } } }; - request.onerror = function(e){ - errorfn(); + errorfn({source: source, error: e}); } + // don't think this works - need to fix or remove + // request.onloadend = function() { + // if(request.status == 404) { + // errorfn({source: source, error: '404'}); + // } + // } request.send(); } From 8279cba460ee9110b1a91b3edff44c0b9f2360f9 Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Wed, 27 Jan 2021 16:33:39 -0800 Subject: [PATCH 14/26] switch to file load/error callbacks, improve logic, add detailed errors, add success/error callback params, add param descriptions #1351 --- plugins/jspsych-preload.js | 197 +++++++++++++++++++++++++++++++------ 1 file changed, 168 insertions(+), 29 deletions(-) diff --git a/plugins/jspsych-preload.js b/plugins/jspsych-preload.js index 03584237..2871b9aa 100644 --- a/plugins/jspsych-preload.js +++ b/plugins/jspsych-preload.js @@ -13,50 +13,93 @@ jsPsych.plugins['preload'] = (function() { parameters: { auto_preload: { type: jsPsych.plugins.parameterType.BOOL, - default: false + default: false, + description: 'Whether or not to automatically preload any media files based on the timeline passed to jsPsych.init.' }, trials: { type: jsPsych.plugins.parameterType.TIMELINE, - default: [] + default: [], + description: 'Array with a timeline of trials to automatically preload. If one or more trial objects is provided, '+ + 'then the plugin will attempt to preload the media files used in the trial(s).' }, images: { type: jsPsych.plugins.parameterType.STRING, - default: [] + default: [], + description: 'Array with one or more image files to load. This parameter is often used in cases where media files cannot '+ + 'be automatically preloaded based on the timeline, e.g. because the media files are passed into an image plugin/parameter with '+ + 'timeline variables or dynamic parameters, or because the image is embedded in an HTML string.' }, audio: { type: jsPsych.plugins.parameterType.STRING, - default: [] + default: [], + description: 'Array with one or more audio files to load. This parameter is often used in cases where media files cannot '+ + 'be automatically preloaded based on the timeline, e.g. because the media files are passed into an audio plugin/parameter with '+ + 'timeline variables or dynamic parameters, or because the audio is embedded in an HTML string.' }, video: { type: jsPsych.plugins.parameterType.STRING, - default: [] + default: [], + description: 'Array with one or more video files to load. This parameter is often used in cases where media files cannot '+ + 'be automatically preloaded based on the timeline, e.g. because the media files are passed into a video plugin/parameter with '+ + 'timeline variables or dynamic parameters, or because the video is embedded in an HTML string.' }, message: { type: jsPsych.plugins.parameterType.HTML_STRING, - default: null + default: null, + description: 'HTML-formatted message to be shown above the progress bar while the files are loading.' }, show_progress_bar: { type: jsPsych.plugins.parameterType.BOOL, default: true, + description: 'Whether or not to show the loading progress bar.' + }, + continue_after_error: { + type: jsPsych.plugins.parameterType.BOOL, + default: false, + description: 'Whether or not to continue with the experiment if a loading error occurs. If false, then if a loading error occurs, '+ + 'the error_message will be shown on the page and the trial will not end. If true, then if if a loading error occurs, the trial will end '+ + 'and preloading failure will be logged in the trial data.' + }, + error_message: { + type: jsPsych.plugins.parameterType.HTML_STRING, + default: 'The experiment failed to load.', + description: 'Error message to show on the page in case of any loading errors. This parameter is only relevant when continue_after_error is false.' }, show_detailed_errors: { type: jsPsych.plugins.parameterType.BOOL, - default: false + default: false, + description: 'Whether or not to show a detailed error message on the page. If true, then detailed error messages will be shown on the '+ + 'page for all files that failed to load, along with the general error_message. This parameter is only relevant when continue_after_error is false.' }, max_load_time: { type: jsPsych.plugins.parameterType.INT, - default: null + default: null, + description: 'The maximum amount of time that the plugin should wait before stopping the preload and either ending the trial '+ + '(if continue_after_error is true) or stopping the experiment with an error message (if continue_after_error is false). '+ + 'If null, the plugin will wait indefintely for the files to load.' + }, + on_error: { + type: jsPsych.plugins.parameterType.FUNCTION, + default: null, + description: 'Function to be called after a file fails to load. The function takes the file name as its only argument.' + }, + on_success: { + type: jsPsych.plugins.parameterType.FUNCTION, + default: null, + description: 'Function to be called after a file loads successfully. The function takes the file name as its only argument.' } } } plugin.trial = function(display_element, trial) { - var success = false; + var success = null; var timeout = false; var failed_images = []; var failed_audio = []; var failed_video = []; + var detailed_errors = []; + var in_safe_mode = jsPsych.getSafeModeStatus(); // create list of media to preload // @@ -82,9 +125,14 @@ jsPsych.plugins['preload'] = (function() { audio = audio.concat(trial.audio); video = video.concat(trial.video); - images = jsPsych.utils.unique(jsPsych.utils.flatten(images)) - audio = jsPsych.utils.unique(jsPsych.utils.flatten(audio)) - video = jsPsych.utils.unique(jsPsych.utils.flatten(video)) + images = jsPsych.utils.unique(jsPsych.utils.flatten(images)); + audio = jsPsych.utils.unique(jsPsych.utils.flatten(audio)); + video = jsPsych.utils.unique(jsPsych.utils.flatten(video)); + + if (in_safe_mode) { + // don't preload video if in safe mode (experiment is running via file protocol) + video = []; + } // render display of message and progress bar @@ -111,6 +159,7 @@ jsPsych.plugins['preload'] = (function() { var total_n = images.length + audio.length + video.length; var loaded = 0; + var loaded_success = 0; function update_loading_progress_bar(){ loaded++; @@ -123,45 +172,135 @@ jsPsych.plugins['preload'] = (function() { } } - function loading_error(e){ - if (e.path[0].localName == "img") { - failed_images.push(e.path[0].currentSrc); - } else if (e.path[0].localName == "audio") { - failed_audio.push(e.path[0].currentSrc); - } else if (e.path[0].localName == "video") { - failed_video.push(e.path[0].currentSrc); + function file_loading_error(e) { + // update progress bar even if there's an error + update_loading_progress_bar(); + // change success flag after first file loading error + if (success == null) { + success = false; + } + var source = "unknown file"; + if (e.source) { + source = e.source; + } + // add file to failed media list + if (e.error && e.error.path && e.error.path.length > 0) { + if (e.error.path[0].localName == "img") { + failed_images.push(source); + } else if (e.error.path[0].localName == "audio") { + failed_audio.push(source); + } else if (e.error.path[0].localName == "video") { + failed_video.push(source); + } + } + // construct detailed error message + var err_msg = '

Error loading file: '+source+'
'; + if (e.error.statusText) { + err_msg += 'File request response status: '+e.error.statusText+'
'; + } + if (typeof e.error.loaded !== 'undefined' && e.error.loaded !== null && e.error.loaded !== 0) { + err_msg += e.error.loaded+' bytes transferred.'; + } else { + err_msg += 'File did not begin loading. Check that file path is correct and reachable by the browser,
'+ + 'and that loading is not blocked by cross-origin resource sharing (CORS) errors.'; + } + err_msg += '

'; + detailed_errors.push(err_msg); + // call trial's on_error function + after_error(source); + // if this is the last file, and if continue_after_error is false, then stop with an error + if (loaded == total_n) { + console.log('last file - error'); + if (trial.continue_after_error) { + end_trial(); + } else { + stop_with_error_message(); + } } } + function file_loading_success(source) { + update_loading_progress_bar(); + // call trial's on_error function + after_success(source); + loaded_success++; + // if this is the last file, then end with success, or stop with an error (if continue_after_error is false) + if (loaded_success == total_n) { + console.log('last file - all successful'); + on_success(); + } else if ((loaded == total_n) && !trial.continue_after_error) { + console.log('last file - success, but at least one error'); + stop_with_error_message(); + } + } + + // media array, callback all complete, file load callback, file error callback function load_video(cb){ - jsPsych.pluginAPI.preloadVideo(video, cb, update_loading_progress_bar, loading_error); + jsPsych.pluginAPI.preloadVideo(video, cb, file_loading_success, file_loading_error); } function load_audio(cb){ - jsPsych.pluginAPI.preloadAudio(audio, cb, update_loading_progress_bar, loading_error); + jsPsych.pluginAPI.preloadAudio(audio, cb, file_loading_success, file_loading_error); } function load_images(cb){ - jsPsych.pluginAPI.preloadImages(images, cb, update_loading_progress_bar, loading_error); + jsPsych.pluginAPI.preloadImages(images, cb, file_loading_success, file_loading_error); } load_video(function(){ load_audio(function(){ - load_images(on_success) + load_images() }) }); function on_success() { - // clear timeout immediately after finishing, to handle race condition with max_load_time - jsPsych.pluginAPI.clearAllTimeouts(); - success = true; - end_trial(); + if (typeof timeout !== 'undefined' && timeout === false) { + // clear timeout immediately after finishing, to handle race condition with max_load_time + jsPsych.pluginAPI.clearAllTimeouts(); + success = true; + end_trial(); + } } function on_timeout() { - if (typeof success !== 'undefined' && success === false) { + console.log('timeout fired'); + if (typeof success !== 'undefined' && (success === false || success === null)) { timeout = true; - end_trial(); + after_error('timeout'); // call trial's on_error event handler here, in case loading timed out with no file errors + detailed_errors.push('

Loading timed out.
'+ + 'Consider compressing your stimuli files, loading your files in smaller batches,
'+ + 'and/or increasing the max_load_time parameter.

'); + if (trial.continue_after_error) { + end_trial(); + } else { + stop_with_error_message(); + } + } + } + + function stop_with_error_message() { + jsPsych.pluginAPI.clearAllTimeouts(); + // show error message + display_element.innerHTML = trial.error_message; + // show detailed errors, if necessary + if (trial.show_detailed_errors) { + display_element.innerHTML += '

Error details:

'; + detailed_errors.forEach(function(e) { + display_element.innerHTML += e; + }); + } + } + + function after_error(source) { + // call on_error function and pass file name + if (trial.on_error !== null) { + trial.on_error(source); + } + } + function after_success(source) { + // call on_success function and pass file name + if (trial.on_success !== null) { + trial.on_success(source); } } From cd5f693a7889619bd3ba96656ccb827bc67ca129 Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Wed, 27 Jan 2021 16:36:09 -0800 Subject: [PATCH 15/26] tweak examples, add console logs in on_success on_error params #1351 --- examples/jspsych-preload.html | 80 +++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/examples/jspsych-preload.html b/examples/jspsych-preload.html index 4fe1ea88..4f88b7f6 100644 --- a/examples/jspsych-preload.html +++ b/examples/jspsych-preload.html @@ -14,7 +14,13 @@ // automatically preload all stimuli files based on the timeline var auto_preload_trial = { type: 'preload', - auto_preload: true + auto_preload: true, + on_error: function(file) { + console.log('Error: ',file); + }, + on_success: function(file) { + console.log('Loaded: ',file); + } }; var start = { @@ -39,29 +45,39 @@ choices: ['Continue'] }; - var start_fail_example = { + var start_fail_example_1 = { type: 'html-button-response', stimulus: '

The next trial is a manual preload trial.

'+ '

Manual preloading allows you to load specific files, for instance if you want to load files in batches,
'+ 'or if the stimuli cannot be automatically preloaded (because you are using timeline variables or dynamic parameters).

'+ - '

In the next preload example, the files in the "images" array do not exist, so the preload will fail.

', + '

In the next preload example, the files in the images array do not exist, so the preload will fail.

'+ + '

The continue_after_error parameter is set to true, so the experiment will continue despite the preload failure.

', choices: ['Next'] }; // manually preload specific files - var preload_fail_example = { + // continue with the experiment even if one or more files fails to load + var preload_fail_example_1 = { type: 'preload', images: ['bad_file_path_1','bad_file_path_2','bad_file_path_3'], audio: ['sound/speech_green.mp3','sound/speech_red.mp3'], - message: '

Please wait while the files load...

(This preload trial will fail after ~10 seconds.)

', - show_detailed_errors: true, - max_load_time: 10000 + message: '

Please wait while the files load.

', + continue_after_error: true, + max_load_time: 3000, + on_error: function(file) { + console.log('Error: ',file); + }, + on_success: function(file) { + console.log('Loaded: ',file); + } }; - var after_fail_example = { + var after_fail_example_1 = { type: 'html-button-response', stimulus: function() { var message; + // if continue_after_error is true in your preload trial, then you can check the trial + // data to see if preloading was successful var preload_trial_data = jsPsych.data.getLastTrialData().values()[0]; if (preload_trial_data.success) { message = '

All files loaded successfully!

'; @@ -74,44 +90,46 @@ choices: ['Next'] }; - var start_timeout_example = { + var start_fail_example_2 = { type: 'html-button-response', stimulus: '

The next trial is another manual preload trial.

'+ - '

It has been set up with a very short max_load_time, so it is likely to timeout before the files finish loading.

', + '

It has been set up with a bad file name, so it will also fail.

'+ + '

This time the continue_after_error parameter is set to false, so the experiment will stop with an error message.

'+ + '

The next trial will also show details about the errors (show_detailed_errors: true),
'+ + 'and print the error to the console (using the on_error function).

', choices: ['Next'] }; - // manually preload specific files - var preload_timeout_example = { + // manually preload specific files + // if loading fails, then stop with a detailed error message + var preload_fail_example_2 = { type: 'preload', images: ['img/happy_face_2.jpg','img/happy_face_3.jpg','img/sad_face_1.jpg','img/sad_face_2.jpg','img/sad_face_3.jpg','img/sad_face_4.jpg', - 'img/1.gif','img/2.gif','img/3.gif','img/4.gif','img/5.gif','img/6.gif','img/7.gif','img/8.gif','img/9.gif','img/10.gif','img/11.gif','img/12.gif'], - message: '

Please wait...

(This preload trial will likely timeout.)

', + 'img/1.gif','img/2.gif','img/3.gif','img/4.gif','img/5.gif','img/6.gif','img/7.gif','img/8.gif','img/9.gif','img/10.gif','img/11.gif','img/12.gif', + 'bad_file_name'], + video: ['video/sample_video.mp4'], + message: '

Please wait...

', show_detailed_errors: true, - max_load_time: 100, - post_trial_gap: 1000 + max_load_time: 1000, + post_trial_gap: 1000, + on_error: function(file) { + console.log('Error: ',file); + }, + on_success: function(file) { + console.log('Loaded: ',file); + } }; - var after_timeout_example = { + var after_fail_example_2 = { type: 'html-button-response', - stimulus: function() { - var message; - var preload_trial_data = jsPsych.data.getLastTrialData().values()[0]; - if (preload_trial_data.success) { - message = '

All files loaded successfully!

'; - } else { - message = '

The files did not finish loading in time.

'+ - '

Click the Next button to see the data.

'; - } - return message; - }, - choices: ['Next'] + stimulus: '

All files loaded successfully!

', + choices: ['End'] }; jsPsych.init({ timeline: [auto_preload_trial, start, image_trial, audio_trial, - start_fail_example, preload_fail_example, after_fail_example, - start_timeout_example, preload_timeout_example, after_timeout_example], + start_fail_example_1, preload_fail_example_1, after_fail_example_1, + start_fail_example_2, preload_fail_example_2, after_fail_example_2], on_finish: function(){jsPsych.data.displayData();} }); From f9b22f040a52205df3538ff6a24c2a93294439a5 Mon Sep 17 00:00:00 2001 From: Becky Gilbert Date: Thu, 28 Jan 2021 15:58:38 -0800 Subject: [PATCH 16/26] tweak preload example file, add comments #1351 --- examples/jspsych-preload.html | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/examples/jspsych-preload.html b/examples/jspsych-preload.html index 4f88b7f6..60c2b14b 100644 --- a/examples/jspsych-preload.html +++ b/examples/jspsych-preload.html @@ -11,6 +11,9 @@