jspsych 7.2.3 → 7.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.browser.js +104 -63
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +1 -1
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +104 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +104 -63
- package/dist/index.js.map +1 -1
- package/dist/modules/extensions.d.ts +1 -1
- package/dist/modules/plugin-api/MediaAPI.d.ts +6 -1
- package/package.json +1 -1
- package/src/JsPsych.ts +65 -47
- package/src/TimelineNode.ts +10 -2
- package/src/modules/extensions.ts +1 -1
- package/src/modules/plugin-api/MediaAPI.ts +41 -21
- package/src/modules/randomization.ts +3 -1
package/dist/index.browser.js
CHANGED
|
@@ -70,7 +70,7 @@ var jsPsychModule = (function (exports) {
|
|
|
70
70
|
return self;
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
-
var version = "7.
|
|
73
|
+
var version = "7.3.1";
|
|
74
74
|
|
|
75
75
|
class MigrationError extends Error {
|
|
76
76
|
constructor(message = "The global `jsPsych` variable is no longer available in jsPsych v7.") {
|
|
@@ -842,8 +842,13 @@ var jsPsychModule = (function (exports) {
|
|
|
842
842
|
this.img_cache = {};
|
|
843
843
|
this.preloadMap = new Map();
|
|
844
844
|
this.microphone_recorder = null;
|
|
845
|
+
this.camera_stream = null;
|
|
846
|
+
this.camera_recorder = null;
|
|
845
847
|
}
|
|
846
848
|
getVideoBuffer(videoID) {
|
|
849
|
+
if (videoID.startsWith("blob:")) {
|
|
850
|
+
this.video_buffers[videoID] = videoID;
|
|
851
|
+
}
|
|
847
852
|
return this.video_buffers[videoID];
|
|
848
853
|
}
|
|
849
854
|
initAudio() {
|
|
@@ -898,15 +903,15 @@ var jsPsychModule = (function (exports) {
|
|
|
898
903
|
callback_error({ source: source, error: e });
|
|
899
904
|
});
|
|
900
905
|
};
|
|
901
|
-
request.onerror =
|
|
906
|
+
request.onerror = (e) => {
|
|
902
907
|
let err = e;
|
|
903
|
-
if (
|
|
908
|
+
if (request.status == 404) {
|
|
904
909
|
err = "404";
|
|
905
910
|
}
|
|
906
911
|
callback_error({ source: source, error: err });
|
|
907
912
|
};
|
|
908
|
-
request.onloadend =
|
|
909
|
-
if (
|
|
913
|
+
request.onloadend = (e) => {
|
|
914
|
+
if (request.status == 404) {
|
|
910
915
|
callback_error({ source: source, error: "404" });
|
|
911
916
|
}
|
|
912
917
|
};
|
|
@@ -963,20 +968,21 @@ var jsPsychModule = (function (exports) {
|
|
|
963
968
|
callback_complete();
|
|
964
969
|
return;
|
|
965
970
|
}
|
|
966
|
-
for (
|
|
967
|
-
|
|
968
|
-
|
|
971
|
+
for (let i = 0; i < images.length; i++) {
|
|
972
|
+
const img = new Image();
|
|
973
|
+
const src = images[i];
|
|
974
|
+
img.onload = () => {
|
|
969
975
|
n_loaded++;
|
|
970
|
-
callback_load(
|
|
976
|
+
callback_load(src);
|
|
971
977
|
if (n_loaded === images.length) {
|
|
972
978
|
callback_complete();
|
|
973
979
|
}
|
|
974
980
|
};
|
|
975
|
-
img.onerror =
|
|
976
|
-
callback_error({ source:
|
|
981
|
+
img.onerror = (e) => {
|
|
982
|
+
callback_error({ source: src, error: e });
|
|
977
983
|
};
|
|
978
|
-
img.src =
|
|
979
|
-
this.img_cache[
|
|
984
|
+
img.src = src;
|
|
985
|
+
this.img_cache[src] = img;
|
|
980
986
|
this.preload_requests.push(img);
|
|
981
987
|
}
|
|
982
988
|
}
|
|
@@ -994,9 +1000,9 @@ var jsPsychModule = (function (exports) {
|
|
|
994
1000
|
const request = new XMLHttpRequest();
|
|
995
1001
|
request.open("GET", video, true);
|
|
996
1002
|
request.responseType = "blob";
|
|
997
|
-
request.onload =
|
|
998
|
-
if (
|
|
999
|
-
const videoBlob =
|
|
1003
|
+
request.onload = () => {
|
|
1004
|
+
if (request.status === 200 || request.status === 0) {
|
|
1005
|
+
const videoBlob = request.response;
|
|
1000
1006
|
video_buffers[video] = URL.createObjectURL(videoBlob); // IE10+
|
|
1001
1007
|
n_loaded++;
|
|
1002
1008
|
callback_load(video);
|
|
@@ -1005,15 +1011,15 @@ var jsPsychModule = (function (exports) {
|
|
|
1005
1011
|
}
|
|
1006
1012
|
}
|
|
1007
1013
|
};
|
|
1008
|
-
request.onerror =
|
|
1014
|
+
request.onerror = (e) => {
|
|
1009
1015
|
let err = e;
|
|
1010
|
-
if (
|
|
1016
|
+
if (request.status == 404) {
|
|
1011
1017
|
err = "404";
|
|
1012
1018
|
}
|
|
1013
1019
|
callback_error({ source: video, error: err });
|
|
1014
1020
|
};
|
|
1015
|
-
request.onloadend =
|
|
1016
|
-
if (
|
|
1021
|
+
request.onloadend = (e) => {
|
|
1022
|
+
if (request.status == 404) {
|
|
1017
1023
|
callback_error({ source: video, error: "404" });
|
|
1018
1024
|
}
|
|
1019
1025
|
};
|
|
@@ -1085,6 +1091,17 @@ var jsPsychModule = (function (exports) {
|
|
|
1085
1091
|
getMicrophoneRecorder() {
|
|
1086
1092
|
return this.microphone_recorder;
|
|
1087
1093
|
}
|
|
1094
|
+
initializeCameraRecorder(stream, opts) {
|
|
1095
|
+
this.camera_stream = stream;
|
|
1096
|
+
const recorder = new MediaRecorder(stream, opts);
|
|
1097
|
+
this.camera_recorder = recorder;
|
|
1098
|
+
}
|
|
1099
|
+
getCameraStream() {
|
|
1100
|
+
return this.camera_stream;
|
|
1101
|
+
}
|
|
1102
|
+
getCameraRecorder() {
|
|
1103
|
+
return this.camera_recorder;
|
|
1104
|
+
}
|
|
1088
1105
|
}
|
|
1089
1106
|
|
|
1090
1107
|
class SimulationAPI {
|
|
@@ -1859,7 +1876,8 @@ var jsPsychModule = (function (exports) {
|
|
|
1859
1876
|
// test to make sure the new neighbor isn't equal to the old one
|
|
1860
1877
|
while (equalityTest(random_shuffle[i + 1], random_shuffle[random_pick]) ||
|
|
1861
1878
|
equalityTest(random_shuffle[i + 1], random_shuffle[random_pick + 1]) ||
|
|
1862
|
-
equalityTest(random_shuffle[i + 1], random_shuffle[random_pick - 1])
|
|
1879
|
+
equalityTest(random_shuffle[i + 1], random_shuffle[random_pick - 1]) ||
|
|
1880
|
+
equalityTest(random_shuffle[i], random_shuffle[random_pick])) {
|
|
1863
1881
|
random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
|
|
1864
1882
|
}
|
|
1865
1883
|
const new_neighbor = random_shuffle[random_pick];
|
|
@@ -2409,7 +2427,11 @@ var jsPsychModule = (function (exports) {
|
|
|
2409
2427
|
// recursive downward search for active trial to extract timeline variable
|
|
2410
2428
|
timelineVariable(variable_name) {
|
|
2411
2429
|
if (typeof this.timeline_parameters == "undefined") {
|
|
2412
|
-
|
|
2430
|
+
const val = this.findTimelineVariable(variable_name);
|
|
2431
|
+
if (typeof val === "undefined") {
|
|
2432
|
+
console.warn("Timeline variable " + variable_name + " not found.");
|
|
2433
|
+
}
|
|
2434
|
+
return val;
|
|
2413
2435
|
}
|
|
2414
2436
|
else {
|
|
2415
2437
|
// if progress.current_location is -1, then the timeline variable is being evaluated
|
|
@@ -2424,7 +2446,11 @@ var jsPsychModule = (function (exports) {
|
|
|
2424
2446
|
loc = loc - 1;
|
|
2425
2447
|
}
|
|
2426
2448
|
// now find the variable
|
|
2427
|
-
|
|
2449
|
+
const val = this.timeline_parameters.timeline[loc].timelineVariable(variable_name);
|
|
2450
|
+
if (typeof val === "undefined") {
|
|
2451
|
+
console.warn("Timeline variable " + variable_name + " not found.");
|
|
2452
|
+
}
|
|
2453
|
+
return val;
|
|
2428
2454
|
}
|
|
2429
2455
|
}
|
|
2430
2456
|
// recursively get all the timeline variables for this trial
|
|
@@ -2702,6 +2728,7 @@ var jsPsychModule = (function (exports) {
|
|
|
2702
2728
|
return this.DOM_container;
|
|
2703
2729
|
}
|
|
2704
2730
|
finishTrial(data = {}) {
|
|
2731
|
+
var _a;
|
|
2705
2732
|
if (this.current_trial_finished) {
|
|
2706
2733
|
return;
|
|
2707
2734
|
}
|
|
@@ -2742,46 +2769,58 @@ var jsPsychModule = (function (exports) {
|
|
|
2742
2769
|
}
|
|
2743
2770
|
}
|
|
2744
2771
|
// handle extension callbacks
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2772
|
+
const extensionCallbackResults = ((_a = current_trial.extensions) !== null && _a !== void 0 ? _a : []).map((extension) => this.extensions[extension.type.info.name].on_finish(extension.params));
|
|
2773
|
+
const onExtensionCallbacksFinished = () => {
|
|
2774
|
+
// about to execute lots of callbacks, so switch context.
|
|
2775
|
+
this.internal.call_immediate = true;
|
|
2776
|
+
// handle callback at plugin level
|
|
2777
|
+
if (typeof current_trial.on_finish === "function") {
|
|
2778
|
+
current_trial.on_finish(trial_data_values);
|
|
2779
|
+
}
|
|
2780
|
+
// handle callback at whole-experiment level
|
|
2781
|
+
this.opts.on_trial_finish(trial_data_values);
|
|
2782
|
+
// after the above callbacks are complete, then the data should be finalized
|
|
2783
|
+
// for this trial. call the on_data_update handler, passing in the same
|
|
2784
|
+
// data object that just went through the trial's finish handlers.
|
|
2785
|
+
this.opts.on_data_update(trial_data_values);
|
|
2786
|
+
// done with callbacks
|
|
2787
|
+
this.internal.call_immediate = false;
|
|
2788
|
+
// wait for iti
|
|
2789
|
+
if (this.simulation_mode === "data-only") {
|
|
2790
|
+
this.nextTrial();
|
|
2749
2791
|
}
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
this.opts.on_trial_finish(trial_data_values);
|
|
2759
|
-
// after the above callbacks are complete, then the data should be finalized
|
|
2760
|
-
// for this trial. call the on_data_update handler, passing in the same
|
|
2761
|
-
// data object that just went through the trial's finish handlers.
|
|
2762
|
-
this.opts.on_data_update(trial_data_values);
|
|
2763
|
-
// done with callbacks
|
|
2764
|
-
this.internal.call_immediate = false;
|
|
2765
|
-
// wait for iti
|
|
2766
|
-
if (this.simulation_mode === "data-only") {
|
|
2767
|
-
this.nextTrial();
|
|
2768
|
-
}
|
|
2769
|
-
else if (typeof current_trial.post_trial_gap === null ||
|
|
2770
|
-
typeof current_trial.post_trial_gap === "undefined") {
|
|
2771
|
-
if (this.opts.default_iti > 0) {
|
|
2772
|
-
setTimeout(this.nextTrial, this.opts.default_iti);
|
|
2792
|
+
else if (typeof current_trial.post_trial_gap === null ||
|
|
2793
|
+
typeof current_trial.post_trial_gap === "undefined") {
|
|
2794
|
+
if (this.opts.default_iti > 0) {
|
|
2795
|
+
setTimeout(this.nextTrial, this.opts.default_iti);
|
|
2796
|
+
}
|
|
2797
|
+
else {
|
|
2798
|
+
this.nextTrial();
|
|
2799
|
+
}
|
|
2773
2800
|
}
|
|
2774
2801
|
else {
|
|
2775
|
-
|
|
2802
|
+
if (current_trial.post_trial_gap > 0) {
|
|
2803
|
+
setTimeout(this.nextTrial, current_trial.post_trial_gap);
|
|
2804
|
+
}
|
|
2805
|
+
else {
|
|
2806
|
+
this.nextTrial();
|
|
2807
|
+
}
|
|
2776
2808
|
}
|
|
2809
|
+
};
|
|
2810
|
+
// Strictly using Promise.resolve to turn all values into promises would be cleaner here, but it
|
|
2811
|
+
// would require user test code to make the event loop tick after every simulated key press even
|
|
2812
|
+
// if there are no async `on_finish` methods. Hence, in order to avoid a breaking change, we
|
|
2813
|
+
// only rely on the event loop if at least one `on_finish` method returns a promise.
|
|
2814
|
+
if (extensionCallbackResults.some((result) => typeof result.then === "function")) {
|
|
2815
|
+
Promise.all(extensionCallbackResults.map((result) => Promise.resolve(result).then((ext_data_values) => {
|
|
2816
|
+
Object.assign(trial_data_values, ext_data_values);
|
|
2817
|
+
}))).then(onExtensionCallbacksFinished);
|
|
2777
2818
|
}
|
|
2778
2819
|
else {
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
}
|
|
2782
|
-
else {
|
|
2783
|
-
this.nextTrial();
|
|
2820
|
+
for (const values of extensionCallbackResults) {
|
|
2821
|
+
Object.assign(trial_data_values, values);
|
|
2784
2822
|
}
|
|
2823
|
+
onExtensionCallbacksFinished();
|
|
2785
2824
|
}
|
|
2786
2825
|
}
|
|
2787
2826
|
endExperiment(end_message = "", data = {}) {
|
|
@@ -3069,16 +3108,16 @@ var jsPsychModule = (function (exports) {
|
|
|
3069
3108
|
}
|
|
3070
3109
|
evaluateTimelineVariables(trial) {
|
|
3071
3110
|
for (const key of Object.keys(trial)) {
|
|
3072
|
-
// timeline variables on the root level
|
|
3073
3111
|
if (typeof trial[key] === "object" &&
|
|
3074
3112
|
trial[key] !== null &&
|
|
3075
3113
|
typeof trial[key].timelineVariablePlaceholder !== "undefined") {
|
|
3076
|
-
|
|
3077
|
-
"function(){returntimeline.timelineVariable(varname);}"
|
|
3078
|
-
)*/ trial[key] = trial[key].timelineVariableFunction();
|
|
3114
|
+
trial[key] = trial[key].timelineVariableFunction();
|
|
3079
3115
|
}
|
|
3080
3116
|
// timeline variables that are nested in objects
|
|
3081
|
-
if (typeof trial[key] === "object" &&
|
|
3117
|
+
if (typeof trial[key] === "object" &&
|
|
3118
|
+
trial[key] !== null &&
|
|
3119
|
+
key !== "timeline" &&
|
|
3120
|
+
key !== "timeline_variables") {
|
|
3082
3121
|
this.evaluateTimelineVariables(trial[key]);
|
|
3083
3122
|
}
|
|
3084
3123
|
}
|
|
@@ -3121,9 +3160,11 @@ var jsPsychModule = (function (exports) {
|
|
|
3121
3160
|
else if (typeof obj === "object") {
|
|
3122
3161
|
if (info === null || !info.nested) {
|
|
3123
3162
|
for (const key of Object.keys(obj)) {
|
|
3124
|
-
if (key === "type") {
|
|
3163
|
+
if (key === "type" || key === "timeline" || key === "timeline_variables") {
|
|
3125
3164
|
// Ignore the object's `type` field because it contains a plugin and we do not want to
|
|
3126
|
-
// call plugin functions
|
|
3165
|
+
// call plugin functions. Also ignore `timeline` and `timeline_variables` because they
|
|
3166
|
+
// are used in the `trials` parameter of the preload plugin and we don't want to actually
|
|
3167
|
+
// evaluate those in that context.
|
|
3127
3168
|
continue;
|
|
3128
3169
|
}
|
|
3129
3170
|
obj[key] = this.replaceFunctionsWithValues(obj[key], null);
|