mirror of
https://github.com/jspsych/jsPsych.git
synced 2025-05-12 16:48:12 +00:00
Merge pull request #1496 from jspsych/feature-preload-plugin
Merge preload plugin branch into master
This commit is contained in:
commit
e81b2b1d37
140
examples/jspsych-preload.html
Normal file
140
examples/jspsych-preload.html
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script src="../jspsych.js"></script>
|
||||||
|
<script src="../plugins/jspsych-preload.js"></script>
|
||||||
|
<script src="../plugins/jspsych-html-button-response.js"></script>
|
||||||
|
<script src="../plugins/jspsych-image-button-response.js"></script>
|
||||||
|
<script src="../plugins/jspsych-audio-button-response.js"></script>
|
||||||
|
<link rel="stylesheet" href="../css/jspsych.css">
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
// all preload files will print information to the console about file loading success/failure
|
||||||
|
// through the on_error and on_success trial parameters
|
||||||
|
|
||||||
|
// automatically preload all stimuli files based on the timeline
|
||||||
|
var auto_preload_trial = {
|
||||||
|
type: 'preload',
|
||||||
|
auto_preload: true,
|
||||||
|
on_error: function(file) {
|
||||||
|
console.log('Error: ',file);
|
||||||
|
},
|
||||||
|
on_success: function(file) {
|
||||||
|
console.log('Loaded: ',file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var start = {
|
||||||
|
type: 'html-button-response',
|
||||||
|
stimulus: '<p>The previous trial was an automatic preload trial.</p>'+
|
||||||
|
'<p>This trial loaded all stimuli files that can be automatically preloaded<br>based on the main jsPsych experiment timeline.</p>'+
|
||||||
|
'<p>In this example experiment, there are two trials in which the stimuli can be automatically preloaded.</p>'+
|
||||||
|
'<p>The stimuli for these trials have finished loading.</p>',
|
||||||
|
choices: ['Next']
|
||||||
|
};
|
||||||
|
|
||||||
|
var image_trial = {
|
||||||
|
type: 'image-button-response',
|
||||||
|
stimulus: 'img/happy_face_1.jpg',
|
||||||
|
choices: ['Happy', 'Sad'],
|
||||||
|
stimulus_width: 400
|
||||||
|
};
|
||||||
|
|
||||||
|
var audio_trial = {
|
||||||
|
type: 'audio-button-response',
|
||||||
|
stimulus: ['sound/speech_green.mp3'],
|
||||||
|
choices: ['Continue']
|
||||||
|
};
|
||||||
|
|
||||||
|
var start_fail_example_1 = {
|
||||||
|
type: 'html-button-response',
|
||||||
|
stimulus: '<p>The next trial is a manual preload trial.</p>'+
|
||||||
|
'<p>Manual preloading allows you to load specific files, for instance if you want to load files in batches,<br>'+
|
||||||
|
'or if the stimuli cannot be automatically preloaded (because you are using timeline variables or dynamic parameters).</p>'+
|
||||||
|
'<p>In the next preload example, the files in the <i>images</i> array do not exist, so the preload will fail.</p>'+
|
||||||
|
'<p>The <i>continue_after_error</i> parameter is set to <i>true</i>, so the experiment will continue despite the preload failure.</p>',
|
||||||
|
choices: ['Next']
|
||||||
|
};
|
||||||
|
|
||||||
|
// manually preload specific files
|
||||||
|
// continue with the experiment even if one or more files fails to load
|
||||||
|
var preload_fail_example_1 = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['BAD_IMAGE_NAME_1','BAD_IMAGE_NAME_2','BAD_IMAGE_NAME_3'],
|
||||||
|
audio: ['sound/speech_green.mp3','sound/speech_red.mp3'],
|
||||||
|
message: '<p>Please wait while the files load.</p>',
|
||||||
|
continue_after_error: true,
|
||||||
|
max_load_time: 4000,
|
||||||
|
on_error: function(file) {
|
||||||
|
console.log('Error: ',file);
|
||||||
|
},
|
||||||
|
on_success: function(file) {
|
||||||
|
console.log('Loaded: ',file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = '<p>All files loaded successfully!</p>';
|
||||||
|
} else {
|
||||||
|
message = '<p>There was a problem loading one or more files.</p>'+
|
||||||
|
'<p>The files that failed to load are listed in the data for the previous trial.</p>';
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
},
|
||||||
|
choices: ['Next']
|
||||||
|
};
|
||||||
|
|
||||||
|
var start_fail_example_2 = {
|
||||||
|
type: 'html-button-response',
|
||||||
|
stimulus: '<p>The next trial is another manual preload trial.</p>'+
|
||||||
|
'<p>It has been set up with bad file names, so it will also fail.</p>'+
|
||||||
|
'<p>This time the <i>continue_after_error</i> parameter is set to <i>false</i>, so the experiment will stop with an error message.</p>'+
|
||||||
|
'<p>The next trial will also show details about the errors (<i>show_detailed_errors: true</i>).</p>',
|
||||||
|
choices: ['Next']
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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',
|
||||||
|
'BAD_IMAGE_NAME'],
|
||||||
|
audio: ['BAD_AUDIO_NAME'],
|
||||||
|
video: ['video/sample_video.mp4', 'BAD_VIDEO_NAME'], // videos will not be preloaded if HTML file is running locally (i.e. safe mode)
|
||||||
|
message: '<p>Please wait...</p>',
|
||||||
|
show_detailed_errors: true,
|
||||||
|
max_load_time: null,
|
||||||
|
post_trial_gap: 1000,
|
||||||
|
on_error: function(file) {
|
||||||
|
console.log('Error: ',file);
|
||||||
|
},
|
||||||
|
on_success: function(file) {
|
||||||
|
console.log('Loaded: ',file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var after_fail_example_2 = {
|
||||||
|
type: 'html-button-response',
|
||||||
|
stimulus: '<p>All files loaded successfully!</p>',
|
||||||
|
choices: ['End']
|
||||||
|
};
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [auto_preload_trial, start, image_trial, audio_trial,
|
||||||
|
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();}
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</html>
|
229
jspsych.js
229
jspsych.js
@ -97,18 +97,11 @@ window.jsPsych = (function() {
|
|||||||
'on_close': function(){
|
'on_close': function(){
|
||||||
return undefined;
|
return undefined;
|
||||||
},
|
},
|
||||||
'preload_images': [],
|
|
||||||
'preload_audio': [],
|
|
||||||
'preload_video': [],
|
|
||||||
'use_webaudio': true,
|
'use_webaudio': true,
|
||||||
'exclusions': {},
|
'exclusions': {},
|
||||||
'show_progress_bar': false,
|
'show_progress_bar': false,
|
||||||
'message_progress_bar': 'Completion Progress',
|
'message_progress_bar': 'Completion Progress',
|
||||||
'auto_update_progress_bar': true,
|
'auto_update_progress_bar': true,
|
||||||
'auto_preload': true,
|
|
||||||
'show_preload_progress_bar': true,
|
|
||||||
'max_load_time': 60000,
|
|
||||||
'max_preload_attempts': 10,
|
|
||||||
'default_iti': 0,
|
'default_iti': 0,
|
||||||
'minimum_valid_rt': 0,
|
'minimum_valid_rt': 0,
|
||||||
'experiment_width': null,
|
'experiment_width': null,
|
||||||
@ -201,23 +194,11 @@ window.jsPsych = (function() {
|
|||||||
checkExclusions(opts.exclusions,
|
checkExclusions(opts.exclusions,
|
||||||
function(){
|
function(){
|
||||||
// success! user can continue...
|
// success! user can continue...
|
||||||
// start experiment, with or without preloading
|
// start experiment
|
||||||
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();
|
startExperiment();
|
||||||
}
|
|
||||||
},
|
},
|
||||||
function(){
|
function(){
|
||||||
// fail. incompatible user.
|
// fail. incompatible user.
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -355,13 +336,6 @@ window.jsPsych = (function() {
|
|||||||
|
|
||||||
core.addNodeToEndOfTimeline = function(new_timeline, preload_callback){
|
core.addNodeToEndOfTimeline = function(new_timeline, preload_callback){
|
||||||
timeline.insert(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(){
|
core.pauseExperiment = function(){
|
||||||
@ -382,6 +356,10 @@ window.jsPsych = (function() {
|
|||||||
DOM_target.innerHTML = message;
|
DOM_target.innerHTML = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
core.getSafeModeStatus = function() {
|
||||||
|
return file_protocol;
|
||||||
|
}
|
||||||
|
|
||||||
function TimelineNode(parameters, parent, relativeID) {
|
function TimelineNode(parameters, parent, relativeID) {
|
||||||
|
|
||||||
// a unique ID for this node, relative to the parent
|
// a unique ID for this node, relative to the parent
|
||||||
@ -1204,7 +1182,8 @@ jsPsych.plugins = (function() {
|
|||||||
AUDIO: 9,
|
AUDIO: 9,
|
||||||
VIDEO: 10,
|
VIDEO: 10,
|
||||||
OBJECT: 11,
|
OBJECT: 11,
|
||||||
COMPLEX: 12
|
COMPLEX: 12,
|
||||||
|
TIMELINE: 13
|
||||||
}
|
}
|
||||||
|
|
||||||
module.universalPluginParameters = {
|
module.universalPluginParameters = {
|
||||||
@ -2489,10 +2468,11 @@ jsPsych.pluginAPI = (function() {
|
|||||||
// preloading stimuli //
|
// preloading stimuli //
|
||||||
|
|
||||||
var preloads = [];
|
var preloads = [];
|
||||||
|
var preload_requests = [];
|
||||||
|
|
||||||
var img_cache = {};
|
var img_cache = {};
|
||||||
|
|
||||||
module.preloadAudioFiles = function(files, callback_complete, callback_load) {
|
module.preloadAudio = function(files, callback_complete, callback_load, callback_error) {
|
||||||
|
|
||||||
files = jsPsych.utils.flatten(files);
|
files = jsPsych.utils.flatten(files);
|
||||||
files = jsPsych.utils.unique(files);
|
files = jsPsych.utils.unique(files);
|
||||||
@ -2500,6 +2480,7 @@ jsPsych.pluginAPI = (function() {
|
|||||||
var n_loaded = 0;
|
var n_loaded = 0;
|
||||||
var loadfn = (typeof callback_load === 'undefined') ? function() {} : callback_load;
|
var loadfn = (typeof callback_load === 'undefined') ? function() {} : callback_load;
|
||||||
var finishfn = (typeof callback_complete === 'undefined') ? function() {} : callback_complete;
|
var finishfn = (typeof callback_complete === 'undefined') ? function() {} : callback_complete;
|
||||||
|
var errorfn = (typeof callback_error === 'undefined') ? function() {} : callback_error;
|
||||||
|
|
||||||
if(files.length==0){
|
if(files.length==0){
|
||||||
finishfn();
|
finishfn();
|
||||||
@ -2515,24 +2496,28 @@ jsPsych.pluginAPI = (function() {
|
|||||||
context.decodeAudioData(request.response, function(buffer) {
|
context.decodeAudioData(request.response, function(buffer) {
|
||||||
audio_buffers[source] = buffer;
|
audio_buffers[source] = buffer;
|
||||||
n_loaded++;
|
n_loaded++;
|
||||||
loadfn(n_loaded);
|
loadfn(source);
|
||||||
if(n_loaded == files.length) {
|
if(n_loaded == files.length) {
|
||||||
finishfn();
|
finishfn();
|
||||||
}
|
}
|
||||||
}, function() {
|
}, function(e) {
|
||||||
console.error('Error loading audio file: ' + bufferID);
|
errorfn({source: source, error: e});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
request.onerror = function(){
|
request.onerror = function(e){
|
||||||
if(count < jsPsych.initSettings().max_preload_attempts){
|
var err = e;
|
||||||
setTimeout(function(){
|
if(this.status == 404) {
|
||||||
load_audio_file_webaudio(source, count+1)
|
err = "404";
|
||||||
}, 200);
|
}
|
||||||
} else {
|
errorfn({source: source, error: err});
|
||||||
jsPsych.loadFail();
|
}
|
||||||
|
request.onloadend = function(e){
|
||||||
|
if(this.status == 404) {
|
||||||
|
errorfn({source: source, error: "404"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.send();
|
request.send();
|
||||||
|
preload_requests.push(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
function load_audio_file_html5audio(source, count){
|
function load_audio_file_html5audio(source, count){
|
||||||
@ -2541,40 +2526,29 @@ jsPsych.pluginAPI = (function() {
|
|||||||
audio.addEventListener('canplaythrough', function handleCanPlayThrough(){
|
audio.addEventListener('canplaythrough', function handleCanPlayThrough(){
|
||||||
audio_buffers[source] = audio;
|
audio_buffers[source] = audio;
|
||||||
n_loaded++;
|
n_loaded++;
|
||||||
loadfn(n_loaded);
|
loadfn(source);
|
||||||
if(n_loaded == files.length){
|
if(n_loaded == files.length){
|
||||||
finishfn();
|
finishfn();
|
||||||
}
|
}
|
||||||
audio.removeEventListener('canplaythrough', handleCanPlayThrough);
|
audio.removeEventListener('canplaythrough', handleCanPlayThrough);
|
||||||
});
|
});
|
||||||
audio.addEventListener('error', function handleError(){
|
audio.addEventListener('error', function handleError(e){
|
||||||
if(count < jsPsych.initSettings().max_preload_attempts){
|
errorfn({source: audio.src, error: e});
|
||||||
setTimeout(function(){
|
|
||||||
load_audio_file_html5audio(source, count+1)
|
|
||||||
}, 200);
|
|
||||||
} else {
|
|
||||||
jsPsych.loadFail();
|
|
||||||
}
|
|
||||||
audio.removeEventListener('error', handleError);
|
audio.removeEventListener('error', handleError);
|
||||||
});
|
});
|
||||||
audio.addEventListener('abort', function handleAbort(){
|
audio.addEventListener('abort', function handleAbort(e){
|
||||||
if(count < jsPsych.initSettings().max_preload_attempts){
|
errorfn({source: audio.src, error: e});
|
||||||
setTimeout(function(){
|
|
||||||
load_audio_file_html5audio(source, count+1)
|
|
||||||
}, 200);
|
|
||||||
} else {
|
|
||||||
jsPsych.loadFail();
|
|
||||||
}
|
|
||||||
audio.removeEventListener('abort', handleAbort);
|
audio.removeEventListener('abort', handleAbort);
|
||||||
});
|
});
|
||||||
audio.src = source;
|
audio.src = source;
|
||||||
|
preload_requests.push(audio);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
for (var i = 0; i < files.length; i++) {
|
||||||
var bufferID = files[i];
|
var bufferID = files[i];
|
||||||
if (typeof audio_buffers[bufferID] !== 'undefined') {
|
if (typeof audio_buffers[bufferID] !== 'undefined') {
|
||||||
n_loaded++;
|
n_loaded++;
|
||||||
loadfn(n_loaded);
|
loadfn(bufferID);
|
||||||
if(n_loaded == files.length) {
|
if(n_loaded == files.length) {
|
||||||
finishfn();
|
finishfn();
|
||||||
}
|
}
|
||||||
@ -2590,47 +2564,41 @@ 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
|
// flatten the images array
|
||||||
images = jsPsych.utils.flatten(images);
|
images = jsPsych.utils.flatten(images);
|
||||||
images = jsPsych.utils.unique(images);
|
images = jsPsych.utils.unique(images);
|
||||||
|
|
||||||
var n_loaded = 0;
|
var n_loaded = 0;
|
||||||
var loadfn = (typeof callback_load === 'undefined') ? function() {} : callback_load;
|
|
||||||
var finishfn = (typeof callback_complete === 'undefined') ? function() {} : callback_complete;
|
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){
|
if(images.length === 0){
|
||||||
finishfn();
|
finishfn();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function preload_image(source, count){
|
function preload_image(source){
|
||||||
count = count || 1;
|
|
||||||
|
|
||||||
var img = new Image();
|
var img = new Image();
|
||||||
|
|
||||||
img.onload = function() {
|
img.onload = function() {
|
||||||
n_loaded++;
|
n_loaded++;
|
||||||
loadfn(n_loaded);
|
loadfn(img.src);
|
||||||
if (n_loaded === images.length) {
|
if (n_loaded === images.length) {
|
||||||
finishfn();
|
finishfn();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
img.onerror = function() {
|
img.onerror = function(e) {
|
||||||
if(count < jsPsych.initSettings().max_preload_attempts){
|
errorfn({source: img.src, error: e});
|
||||||
setTimeout(function(){
|
|
||||||
preload_image(source, count+1);
|
|
||||||
}, 200);
|
|
||||||
} else {
|
|
||||||
jsPsych.loadFail();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
img.src = source;
|
img.src = source;
|
||||||
|
|
||||||
img_cache[source] = img;
|
img_cache[source] = img;
|
||||||
|
preload_requests.push(img);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < images.length; i++) {
|
for (var i = 0; i < images.length; i++) {
|
||||||
@ -2639,15 +2607,16 @@ 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
|
// flatten the video array
|
||||||
video = jsPsych.utils.flatten(video);
|
video = jsPsych.utils.flatten(video);
|
||||||
video = jsPsych.utils.unique(video);
|
video = jsPsych.utils.unique(video);
|
||||||
|
|
||||||
var n_loaded = 0;
|
var n_loaded = 0;
|
||||||
var loadfn = !callback_load ? function() {} : callback_load;
|
|
||||||
var finishfn = !callback_complete ? function() {} : callback_complete;
|
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){
|
if(video.length===0){
|
||||||
finishfn();
|
finishfn();
|
||||||
@ -2665,23 +2634,26 @@ jsPsych.pluginAPI = (function() {
|
|||||||
var videoBlob = this.response;
|
var videoBlob = this.response;
|
||||||
video_buffers[source] = URL.createObjectURL(videoBlob); // IE10+
|
video_buffers[source] = URL.createObjectURL(videoBlob); // IE10+
|
||||||
n_loaded++;
|
n_loaded++;
|
||||||
loadfn(n_loaded);
|
loadfn(source);
|
||||||
if (n_loaded === video.length) {
|
if (n_loaded === video.length) {
|
||||||
finishfn();
|
finishfn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
request.onerror = function(e){
|
||||||
request.onerror = function(){
|
var err = e;
|
||||||
if(count < jsPsych.initSettings().max_preload_attempts){
|
if(this.status == 404) {
|
||||||
setTimeout(function(){
|
err = "404";
|
||||||
preload_video(source, count+1)
|
}
|
||||||
}, 200);
|
errorfn({source: source, error: err});
|
||||||
} else {
|
}
|
||||||
jsPsych.loadFail();
|
request.onloadend = function(e){
|
||||||
|
if(this.status == 404) {
|
||||||
|
errorfn({source: source, error: "404"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
request.send();
|
request.send();
|
||||||
|
preload_requests.push(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < video.length; i++) {
|
for (var i = 0; i < video.length; i++) {
|
||||||
@ -2690,7 +2662,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) {
|
if (['audio', 'image', 'video'].indexOf(media_type)===-1) {
|
||||||
console.error('Invalid media_type parameter for jsPsych.pluginAPI.registerPreload. Please check the plugin file.');
|
console.error('Invalid media_type parameter for jsPsych.pluginAPI.registerPreload. Please check the plugin file.');
|
||||||
}
|
}
|
||||||
@ -2698,33 +2670,59 @@ jsPsych.pluginAPI = (function() {
|
|||||||
var preload = {
|
var preload = {
|
||||||
plugin: plugin_name,
|
plugin: plugin_name,
|
||||||
parameter: parameter,
|
parameter: parameter,
|
||||||
media_type: media_type,
|
media_type: media_type
|
||||||
conditional_function: conditional_function
|
|
||||||
}
|
}
|
||||||
|
|
||||||
preloads.push(preload);
|
preloads.push(preload);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.autoPreload = function(timeline, callback, file_protocol, images, audio, video, progress_bar) {
|
module.getAutoPreloadList = function(timeline_description){
|
||||||
|
|
||||||
|
function getTrialsOfTypeFromTimelineDescription(td, target_type, inherited_type){
|
||||||
|
var trials = [];
|
||||||
|
|
||||||
|
for(var i=0; i<td.length; i++){
|
||||||
|
var node = td[i];
|
||||||
|
if(Array.isArray(node.timeline)){
|
||||||
|
if(typeof node.type !== 'undefined'){
|
||||||
|
inherited_type = node.type;
|
||||||
|
}
|
||||||
|
trials = trials.concat(getTrialsOfTypeFromTimelineDescription(node.timeline, target_type, inherited_type));
|
||||||
|
} else {
|
||||||
|
if(typeof node.type !== 'undefined' && node.type == target_type){
|
||||||
|
trials.push(node);
|
||||||
|
}
|
||||||
|
if(typeof node.type == 'undefined' && inherited_type == target_type){
|
||||||
|
trials.push(Object.assign({}, {type: target_type}, node));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trials;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(typeof timeline_description == 'undefined'){
|
||||||
|
timeline_description = jsPsych.initSettings().timeline;
|
||||||
|
}
|
||||||
|
|
||||||
// list of items to preload
|
// list of items to preload
|
||||||
images = images || [];
|
var images = [];
|
||||||
audio = audio || [];
|
var audio = [];
|
||||||
video = video || [];
|
var video = [];
|
||||||
|
|
||||||
// construct list
|
// construct list
|
||||||
for (var i = 0; i < preloads.length; i++) {
|
for (var i = 0; i < preloads.length; i++) {
|
||||||
var type = preloads[i].plugin;
|
var type = preloads[i].plugin;
|
||||||
var param = preloads[i].parameter;
|
var param = preloads[i].parameter;
|
||||||
var media = preloads[i].media_type;
|
var media = preloads[i].media_type;
|
||||||
var func = preloads[i].conditional_function;
|
|
||||||
var trials = timeline.trialsOfType(type);
|
var trials = getTrialsOfTypeFromTimelineDescription(timeline_description, type);
|
||||||
for (var j = 0; j < trials.length; j++) {
|
for (var j = 0; j < trials.length; j++) {
|
||||||
|
|
||||||
if (typeof trials[j][param] == 'undefined') {
|
if (typeof trials[j][param] == 'undefined') {
|
||||||
console.warn("jsPsych failed to auto preload one or more files:");
|
console.warn("jsPsych failed to auto preload one or more files:");
|
||||||
console.warn("no parameter called "+param+" in plugin "+type);
|
console.warn("no parameter called "+param+" in plugin "+type);
|
||||||
} else if (typeof trials[j][param] !== 'function') {
|
} else if (typeof trials[j][param] !== 'function') {
|
||||||
if ( !func || func(trials[j]) ){
|
|
||||||
if (media === 'image') {
|
if (media === 'image') {
|
||||||
images = images.concat(jsPsych.utils.flatten([trials[j][param]]));
|
images = images.concat(jsPsych.utils.flatten([trials[j][param]]));
|
||||||
} else if (media === 'audio') {
|
} else if (media === 'audio') {
|
||||||
@ -2735,7 +2733,6 @@ jsPsych.pluginAPI = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
images = jsPsych.utils.unique(jsPsych.utils.flatten(images));
|
images = jsPsych.utils.unique(jsPsych.utils.flatten(images));
|
||||||
audio = jsPsych.utils.unique(jsPsych.utils.flatten(audio));
|
audio = jsPsych.utils.unique(jsPsych.utils.flatten(audio));
|
||||||
@ -2744,45 +2741,21 @@ jsPsych.pluginAPI = (function() {
|
|||||||
// remove any nulls false values
|
// remove any nulls false values
|
||||||
images = images.filter(function(x) { return x != false && x != null})
|
images = images.filter(function(x) { return x != false && x != null})
|
||||||
audio = audio.filter(function(x) { return x != false && x != null})
|
audio = audio.filter(function(x) { return x != false && x != null})
|
||||||
// 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})
|
video = video.filter(function(x) { return x != false && x != null})
|
||||||
}
|
|
||||||
|
|
||||||
var total_n = images.length + audio.length + video.length;
|
return {
|
||||||
|
images, audio, video
|
||||||
var loaded = 0;
|
|
||||||
|
|
||||||
if(progress_bar){
|
|
||||||
var pb_html = "<div id='jspsych-loading-progress-bar-container' style='height: 10px; width: 300px; background-color: #ddd; margin: auto;'>";
|
|
||||||
pb_html += "<div id='jspsych-loading-progress-bar' style='height: 10px; width: 0%; background-color: #777;'></div>";
|
|
||||||
pb_html += "</div>";
|
|
||||||
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
|
module.cancelPreloads = function() {
|
||||||
// first the images, then when the images are complete
|
for(var i=0;i<preload_requests.length; i++){
|
||||||
// wait for the audio files to finish
|
preload_requests[i].onload = function() {};
|
||||||
module.preloadImages(images, function() {
|
preload_requests[i].onerror = function() {};
|
||||||
module.preloadAudioFiles(audio, function() {
|
preload_requests[i].oncanplaythrough = function() {};
|
||||||
module.preloadVideo(video, function() {
|
preload_requests[i].onabort = function() {};
|
||||||
callback();
|
}
|
||||||
}, update_loading_progress_bar);
|
preload_requests = [];
|
||||||
}, update_loading_progress_bar);
|
|
||||||
}, update_loading_progress_bar);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
344
plugins/jspsych-preload.js
Normal file
344
plugins/jspsych-preload.js
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
/**
|
||||||
|
* 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,
|
||||||
|
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: [],
|
||||||
|
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: [],
|
||||||
|
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: [],
|
||||||
|
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: [],
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
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,
|
||||||
|
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 = 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 //
|
||||||
|
|
||||||
|
var images = [];
|
||||||
|
var audio = [];
|
||||||
|
var 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));
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
var html = '';
|
||||||
|
|
||||||
|
if(trial.message !== null){
|
||||||
|
html += trial.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(trial.show_progress_bar){
|
||||||
|
html += `
|
||||||
|
<div id='jspsych-loading-progress-bar-container' style='height: 10px; width: 300px; background-color: #ddd; margin: auto;'>
|
||||||
|
<div id='jspsych-loading-progress-bar' style='height: 10px; width: 0%; background-color: #777;'></div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
display_element.innerHTML = html;
|
||||||
|
|
||||||
|
// do preloading
|
||||||
|
|
||||||
|
if(trial.max_load_time !== null){
|
||||||
|
jsPsych.pluginAPI.setTimeout(on_timeout, trial.max_load_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
var total_n = images.length + audio.length + video.length;
|
||||||
|
var loaded = 0; // success or error count
|
||||||
|
var loaded_success = 0; // success count
|
||||||
|
|
||||||
|
function load_video(cb){
|
||||||
|
jsPsych.pluginAPI.preloadVideo(video, cb, file_loading_success, file_loading_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_audio(cb){
|
||||||
|
jsPsych.pluginAPI.preloadAudio(audio, cb, file_loading_success, file_loading_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_images(cb){
|
||||||
|
jsPsych.pluginAPI.preloadImages(images, cb, file_loading_success, file_loading_error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (video.length > 0) { load_video(function () { }) }
|
||||||
|
if (audio.length > 0) { load_audio(function () { }) }
|
||||||
|
if (images.length > 0) { load_images(function () { }) }
|
||||||
|
|
||||||
|
// helper functions and callbacks
|
||||||
|
|
||||||
|
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+"%";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// called when a single file loading fails
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
// add file to failed media list
|
||||||
|
var source = "unknown file";
|
||||||
|
if (e.source) {
|
||||||
|
source = e.source;
|
||||||
|
}
|
||||||
|
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 = '<p><strong>Error loading file: '+source+'</strong><br>';
|
||||||
|
if (e.error.statusText) {
|
||||||
|
err_msg += 'File request response status: '+e.error.statusText+'<br>';
|
||||||
|
}
|
||||||
|
if (e.error == "404") {
|
||||||
|
err_msg += '404 - file not found.<br>';
|
||||||
|
}
|
||||||
|
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,<br>'+
|
||||||
|
'and that loading is not blocked by cross-origin resource sharing (CORS) errors.';
|
||||||
|
}
|
||||||
|
err_msg += '</p>';
|
||||||
|
detailed_errors.push(err_msg);
|
||||||
|
// call trial's on_error function
|
||||||
|
after_error(source);
|
||||||
|
// if this is the last file
|
||||||
|
if (loaded == total_n) {
|
||||||
|
if (trial.continue_after_error) {
|
||||||
|
// if continue_after_error is false, then stop with an error
|
||||||
|
end_trial();
|
||||||
|
} else {
|
||||||
|
// otherwise end the trial and continue
|
||||||
|
stop_with_error_message();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// called when a single file loads successfully
|
||||||
|
function file_loading_success(source) {
|
||||||
|
update_loading_progress_bar();
|
||||||
|
// call trial's on_success function
|
||||||
|
after_success(source);
|
||||||
|
loaded_success++;
|
||||||
|
if (loaded_success == total_n) {
|
||||||
|
// if this is the last file and all loaded successfully, call success function
|
||||||
|
on_success();
|
||||||
|
} else if (loaded == total_n) {
|
||||||
|
// if this is the last file and there was at least one error
|
||||||
|
if (trial.continue_after_error) {
|
||||||
|
// end the trial and continue with experiment
|
||||||
|
end_trial();
|
||||||
|
} else {
|
||||||
|
// if continue_after_error is false, then stop with an error
|
||||||
|
stop_with_error_message();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// called if all files load successfully
|
||||||
|
function on_success() {
|
||||||
|
if (typeof timeout !== 'undefined' && timeout === false) {
|
||||||
|
// clear timeout immediately after finishing, to handle race condition with max_load_time
|
||||||
|
jsPsych.pluginAPI.clearAllTimeouts();
|
||||||
|
// need to call cancel preload function to clear global jsPsych preload_request list, even when they've all succeeded
|
||||||
|
jsPsych.pluginAPI.cancelPreloads();
|
||||||
|
success = true;
|
||||||
|
end_trial();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// called if all_files haven't finished loading when max_load_time is reached
|
||||||
|
function on_timeout() {
|
||||||
|
//console.log('timeout fired');
|
||||||
|
jsPsych.pluginAPI.cancelPreloads();
|
||||||
|
if (typeof success !== 'undefined' && (success === false || success === null)) {
|
||||||
|
timeout = true;
|
||||||
|
if (loaded_success < total_n) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
after_error('timeout'); // call trial's on_error event handler here, in case loading timed out with no file errors
|
||||||
|
detailed_errors.push('<p><strong>Loading timed out.</strong><br>'+
|
||||||
|
'Consider compressing your stimuli files, loading your files in smaller batches,<br>'+
|
||||||
|
'and/or increasing the <i>max_load_time</i> parameter.</p>');
|
||||||
|
if (trial.continue_after_error) {
|
||||||
|
end_trial();
|
||||||
|
} else {
|
||||||
|
stop_with_error_message();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop_with_error_message() {
|
||||||
|
jsPsych.pluginAPI.clearAllTimeouts();
|
||||||
|
jsPsych.pluginAPI.cancelPreloads();
|
||||||
|
// show error message
|
||||||
|
display_element.innerHTML = trial.error_message;
|
||||||
|
// show detailed errors, if necessary
|
||||||
|
if (trial.show_detailed_errors) {
|
||||||
|
display_element.innerHTML += '<p><strong>Error details:</strong></p>';
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return plugin;
|
||||||
|
})();
|
||||||
|
|
43
tests/jsPsych.pluginAPI/preloads.test.js
Normal file
43
tests/jsPsych.pluginAPI/preloads.test.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
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('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');
|
||||||
|
|
||||||
|
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');
|
||||||
|
})
|
||||||
|
})
|
@ -535,7 +535,7 @@ describe('nested timelines', function() {
|
|||||||
|
|
||||||
describe('add node to end of timeline', function(){
|
describe('add node to end of timeline', function(){
|
||||||
|
|
||||||
test('adds node to end of timeline, without callback', function() {
|
test('adds node to end of timeline', function() {
|
||||||
var new_trial = {
|
var new_trial = {
|
||||||
type: 'html-keyboard-response',
|
type: 'html-keyboard-response',
|
||||||
stimulus: 'bar'
|
stimulus: 'bar'
|
||||||
@ -565,31 +565,5 @@ describe('add node to end of timeline', function(){
|
|||||||
utils.pressKey('a');
|
utils.pressKey('a');
|
||||||
});
|
});
|
||||||
|
|
||||||
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('a');
|
|
||||||
expect(jsPsych.getDisplayElement().innerHTML).toMatch('bar');
|
|
||||||
utils.pressKey('a');
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 5.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
Binary file not shown.
916
tests/plugins/plugin-preload.test.js
Normal file
916
tests/plugins/plugin-preload.test.js
Normal file
@ -0,0 +1,916 @@
|
|||||||
|
const root = '../../';
|
||||||
|
|
||||||
|
describe('preload plugin', function () {
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
require(root + 'jspsych.js');
|
||||||
|
require(root + 'plugins/jspsych-preload.js');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
})
|
||||||
|
|
||||||
|
test('loads correctly', function () {
|
||||||
|
expect(typeof window.jsPsych.plugins['preload']).not.toBe('undefined');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('auto_preload', function() {
|
||||||
|
|
||||||
|
test('auto_preload method works with simple timeline and image stimulus', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = 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']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auto_preload method works with simple timeline and audio stimulus', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-audio-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadAudio = jest.fn((x, cb) => { cb(); });
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
auto_preload: true
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'audio-keyboard-response',
|
||||||
|
stimulus: 'sound/foo.mp3',
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadAudio.mock.calls[0][0]).toStrictEqual(['sound/foo.mp3']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auto_preload method works with simple timeline and video stimulus', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-video-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadVideo = jest.fn((x, cb) => { cb(); });
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
auto_preload: true
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'video-keyboard-response',
|
||||||
|
stimulus: 'video/foo.mp4'
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadVideo.mock.calls[0][0]).toStrictEqual(['video/foo.mp4']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auto_preload method works with nested timeline', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb) => { cb(); });
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
auto_preload: true
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
render_on_canvas: false,
|
||||||
|
timeline: [
|
||||||
|
{stimulus: 'img/foo.png'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toStrictEqual(['img/foo.png']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auto_preload method works with looping timeline', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = 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
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
var loop = {
|
||||||
|
timeline: [trial],
|
||||||
|
loop_function: function() {
|
||||||
|
if (count == 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, loop]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toStrictEqual(['img/foo.png']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auto_preload method works with conditional timeline', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = 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
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
var conditional = {
|
||||||
|
timeline: [trial],
|
||||||
|
conditional_function: function() {
|
||||||
|
if (count == 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, conditional]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toStrictEqual(['img/foo.png']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('auto_preload method works with timeline variables when stim is statically defined in trial object', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = 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,
|
||||||
|
data: jsPsych.timelineVariable('data')
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial_procedure = {
|
||||||
|
timeline: [trial],
|
||||||
|
timeline_variables: [
|
||||||
|
{data: {trial: 1}},
|
||||||
|
{data: {trial: 2}},
|
||||||
|
{data: {trial: 3}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial_procedure]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toStrictEqual(['img/foo.png']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('trials parameter', function() {
|
||||||
|
|
||||||
|
test('trials parameter works with simple timeline', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb) => { cb(); });
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'img/foo.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
trials: [trial]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toStrictEqual(['img/foo.png']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('trials parameter works with looping timeline', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb) => { cb(); });
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'img/foo.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
var loop = {
|
||||||
|
timeline: [trial],
|
||||||
|
loop_function: function() {
|
||||||
|
if (count == 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
trials: [loop]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toStrictEqual(['img/foo.png']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('trials parameter works with conditional timeline', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb) => { cb(); });
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'img/foo.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
var conditional = {
|
||||||
|
timeline: [trial],
|
||||||
|
conditional_function: function() {
|
||||||
|
if (count == 0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
trials: [conditional]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toStrictEqual(['img/foo.png']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('trials parameter works with timeline variables when stim is statically defined in trial object', function () {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb) => { cb(); });
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'img/foo.png',
|
||||||
|
render_on_canvas: false,
|
||||||
|
data: jsPsych.timelineVariable('data')
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial_procedure = {
|
||||||
|
timeline: [trial],
|
||||||
|
timeline_variables: [
|
||||||
|
{data: {trial: 1}},
|
||||||
|
{data: {trial: 2}},
|
||||||
|
{data: {trial: 3}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
trials: [trial_procedure]
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toStrictEqual(['img/foo.png']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('calls to pluginAPI preload functions', function() {
|
||||||
|
|
||||||
|
test('auto_preload, trials, and manual preload array parameters can be used together', function () {
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb) => { cb(); });
|
||||||
|
|
||||||
|
var trial_1 = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'img/foo.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial_2 = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'img/bar.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
auto_preload: true,
|
||||||
|
trials: [trial_2],
|
||||||
|
images: ['img/fizz.png']
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial_1]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls.length).toBe(1);
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0].length).toBe(3);
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toContain('img/foo.png');
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toContain('img/bar.png');
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toContain('img/fizz.png');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('plugin only attempts to load duplicate files once', function () {
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb) => { cb(); });
|
||||||
|
|
||||||
|
var trial_1 = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'img/foo.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial_2 = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'img/foo.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
trials: [trial_2],
|
||||||
|
images: ['img/foo.png']
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial_1]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls.length).toBe(1);
|
||||||
|
expect(jsPsych.pluginAPI.preloadImages.mock.calls[0][0]).toStrictEqual(['img/foo.png']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('continue_after_error and error messages', function() {
|
||||||
|
|
||||||
|
test('experiment continues when image loads successfully', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb_complete, cb_load, cb_error) => { cb_load(); cb_complete(); });
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
auto_preload: true,
|
||||||
|
error_message: 'foo',
|
||||||
|
max_load_time: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'image.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('<img src=\"image.png\" id=\"jspsych-image-keyboard-response-stimulus\"');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('error_message is shown when continue_after_error is false and files fail', function() {
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
cb_error({
|
||||||
|
source: x,
|
||||||
|
error: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
auto_preload: true,
|
||||||
|
error_message: 'foo',
|
||||||
|
max_load_time: 100,
|
||||||
|
on_error: function(e) {
|
||||||
|
expect(e).toContain('img/bar.png');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'img/bar.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('error_message is shown when continue_after_error is false and loading times out', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
var mock_fn = jest.fn(function(x) {return x;});
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
// don't return anything here to simulate waiting forever for image to load
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
auto_preload: true,
|
||||||
|
error_message: 'foo',
|
||||||
|
max_load_time: 100,
|
||||||
|
on_error: function(e) {
|
||||||
|
mock_fn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'blue.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(101);
|
||||||
|
|
||||||
|
expect(mock_fn).toHaveBeenCalledWith('timeout');
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('foo');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('experiment continues when continue_after_error is true and files fail', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
var mock_fn = jest.fn(function(x) {return x;});
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
cb_error({
|
||||||
|
source: x,
|
||||||
|
error: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['img/foo.png'],
|
||||||
|
error_message: 'bar',
|
||||||
|
max_load_time: null,
|
||||||
|
continue_after_error: true,
|
||||||
|
on_error: function(e) {
|
||||||
|
mock_fn('loading failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: 'blue.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
expect(mock_fn).toHaveBeenCalledWith('loading failed');
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('<img src=\"blue.png\" id=\"jspsych-image-keyboard-response-stimulus\"');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('experiment continues when continue_after_error is true and loading times out', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
var mock_fn = jest.fn(function(x) {return x;});
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
// don't return anything here to simulate waiting forever for image to load
|
||||||
|
});
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
auto_preload: true,
|
||||||
|
error_message: 'bar',
|
||||||
|
max_load_time: 100,
|
||||||
|
continue_after_error: true,
|
||||||
|
on_error: function(e) {
|
||||||
|
mock_fn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var trial = {
|
||||||
|
type: 'image-keyboard-response',
|
||||||
|
stimulus: '../media/blue.png',
|
||||||
|
render_on_canvas: false
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload, trial]
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(101);
|
||||||
|
|
||||||
|
expect(mock_fn).toHaveBeenCalledWith('timeout');
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('<img src=\"../media/blue.png\" id=\"jspsych-image-keyboard-response-stimulus\"');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('detailed error message is shown when continue_after_error is false and show_detailed_errors is true', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
var mock_fn = jest.fn(function(x) {return x;});
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
cb_error({
|
||||||
|
source: x,
|
||||||
|
error: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['img/foo.png'],
|
||||||
|
error_message: 'bar',
|
||||||
|
show_detailed_errors: true,
|
||||||
|
on_error: function(e) {
|
||||||
|
mock_fn('loading failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload]
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
expect(mock_fn).toHaveBeenCalledWith('loading failed');
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('Error details');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('display while loading', function() {
|
||||||
|
|
||||||
|
test('custom loading message is shown above progress bar if specified', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['img/foo.png'],
|
||||||
|
message: 'bar',
|
||||||
|
max_load_time: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('bar');
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('<div id=\"jspsych-loading-progress-bar-container');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('progress bar is shown without message by default', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['img/foo.png'],
|
||||||
|
max_load_time: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('<div id=\"jspsych-loading-progress-bar-container');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('progress bar is not shown if show_progress_bar is false', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['img/foo.png'],
|
||||||
|
show_progress_bar: false,
|
||||||
|
max_load_time: 100
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload]
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).not.toMatch('<div id=\"jspsych-loading-progress-bar-container');
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('on_success and on_error parameters', function() {
|
||||||
|
|
||||||
|
test('on_error/on_success callbacks are called during preload trial after each loading success/error', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
var mock_fn = jest.fn(function(x) {return x;});
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
if(x.includes('blue.png')){
|
||||||
|
cb_load();
|
||||||
|
cb_complete();
|
||||||
|
} else {
|
||||||
|
cb_error({
|
||||||
|
source: x,
|
||||||
|
error: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
jsPsych.pluginAPI.preloadVideo = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
cb_error({
|
||||||
|
source: x,
|
||||||
|
error: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
jsPsych.pluginAPI.preloadAudio = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
cb_error({
|
||||||
|
source: x,
|
||||||
|
error: {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var preload_1 = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['foo.png'],
|
||||||
|
audio: ['bar.mp3'],
|
||||||
|
video: ['buzz.mp4'],
|
||||||
|
continue_after_error: true,
|
||||||
|
on_error: function(e) {
|
||||||
|
mock_fn('loading failed');
|
||||||
|
},
|
||||||
|
on_success: function(e) {
|
||||||
|
mock_fn('loading succeeded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var preload_2 = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['blue.png'],
|
||||||
|
max_load_time: 100,
|
||||||
|
on_error: function(e) {
|
||||||
|
mock_fn('loading failed');
|
||||||
|
},
|
||||||
|
on_success: function(e) {
|
||||||
|
mock_fn('loading succeeded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload_1, preload_2]
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
expect(mock_fn.mock.calls[0][0]).toBe('loading failed');
|
||||||
|
expect(mock_fn.mock.calls[1][0]).toBe('loading failed');
|
||||||
|
expect(mock_fn.mock.calls[2][0]).toBe('loading failed');
|
||||||
|
expect(mock_fn.mock.calls[3][0]).toBe('loading succeeded');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('on_error/on_success callbacks are not called after loading times out', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
var mock_fn = jest.fn(function(x) {return x;});
|
||||||
|
var cancel_preload_spy = jest.spyOn(jsPsych.pluginAPI, 'cancelPreloads');
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
// empty to simulate timeout
|
||||||
|
});
|
||||||
|
jsPsych.pluginAPI.preloadVideo = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
// empty to simulate timeout
|
||||||
|
});
|
||||||
|
jsPsych.pluginAPI.preloadAudio = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
// empty to simulate timeout
|
||||||
|
});
|
||||||
|
jest.useFakeTimers();
|
||||||
|
|
||||||
|
var preload = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['img/foo.png', 'blue.png'],
|
||||||
|
audio: ['audio/bar.mp3'],
|
||||||
|
video: ['video/buzz.mp4'],
|
||||||
|
continue_after_error: true,
|
||||||
|
max_load_time: 100,
|
||||||
|
on_error: function(e) {
|
||||||
|
if (e == "timeout") {
|
||||||
|
mock_fn(e);
|
||||||
|
} else {
|
||||||
|
mock_fn('loading failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
on_success: function(e) {
|
||||||
|
mock_fn('loading succeeded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload]
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(101);
|
||||||
|
|
||||||
|
expect(mock_fn).toHaveBeenCalledWith('timeout');
|
||||||
|
expect(mock_fn).toHaveBeenLastCalledWith('timeout');
|
||||||
|
expect(cancel_preload_spy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('experiment stops with default error_message and on_error/on_success callbacks are not called after preload trial ends with error', function() {
|
||||||
|
|
||||||
|
require(root + 'plugins/jspsych-image-keyboard-response.js');
|
||||||
|
|
||||||
|
var mock_fn = jest.fn(function(x) {return x;});
|
||||||
|
var cancel_preload_spy = jest.spyOn(jsPsych.pluginAPI,'cancelPreloads');
|
||||||
|
jest.useFakeTimers();
|
||||||
|
jsPsych.pluginAPI.preloadImages = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
if(x.includes('blue.png')){
|
||||||
|
cb_load();
|
||||||
|
cb_complete();
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
jsPsych.pluginAPI.preloadVideo = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
|
||||||
|
});
|
||||||
|
jsPsych.pluginAPI.preloadAudio = jest.fn((x, cb_complete, cb_load, cb_error) => {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
var preload_1 = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['img/foo.png'],
|
||||||
|
audio: ['audio/bar.mp3'],
|
||||||
|
video: ['video/buzz.mp4'],
|
||||||
|
max_load_time: 100,
|
||||||
|
on_error: function(e) {
|
||||||
|
if (e == 'timeout') {
|
||||||
|
mock_fn(e);
|
||||||
|
} else {
|
||||||
|
mock_fn('loading failed');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
on_success: function(e) {
|
||||||
|
mock_fn('loading succeeded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var preload_2 = {
|
||||||
|
type: 'preload',
|
||||||
|
images: ['../media/blue.png'],
|
||||||
|
max_load_time: 100,
|
||||||
|
on_error: function(e) {
|
||||||
|
mock_fn('loading failed');
|
||||||
|
},
|
||||||
|
on_success: function(e) {
|
||||||
|
mock_fn('loading succeeded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsPsych.init({
|
||||||
|
timeline: [preload_1, preload_2]
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.advanceTimersByTime(101);
|
||||||
|
|
||||||
|
expect(mock_fn).toHaveBeenCalledWith('timeout');
|
||||||
|
expect(mock_fn).toHaveBeenLastCalledWith('timeout');
|
||||||
|
expect(jsPsych.getDisplayElement().innerHTML).toMatch('The experiment failed to load.');
|
||||||
|
expect(cancel_preload_spy).toHaveBeenCalled();
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user