1
0
mirror of https://github.com/psychopy/psychojs.git synced 2025-05-10 18:50:54 +00:00

Merge pull request #455 from apitiot/2021.2.3

ServerManager: waiting for completion of media upload
This commit is contained in:
Alain Pitiot 2021-08-11 13:18:06 +02:00 committed by GitHub
commit cfeaf3a9e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 94 additions and 46 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "psychojs", "name": "psychojs",
"version": "2021.2.2", "version": "2021.2.3",
"private": true, "private": true,
"description": "Helps run in-browser neuroscience, psychology, and psychophysics experiments", "description": "Helps run in-browser neuroscience, psychology, and psychophysics experiments",
"license": "MIT", "license": "MIT",

View File

@ -176,7 +176,7 @@ export class PsychoJS
} }
this.logger.info("[PsychoJS] Initialised."); this.logger.info("[PsychoJS] Initialised.");
this.logger.info("[PsychoJS] @version 2021.2.2"); this.logger.info("[PsychoJS] @version 2021.2.3");
// hide the initialisation message: // hide the initialisation message:
jQuery("#root").addClass("is-ready"); jQuery("#root").addClass("is-ready");

View File

@ -27,7 +27,7 @@ import { PsychoJS } from "./PsychoJS.js";
*/ */
export class ServerManager extends PsychObject export class ServerManager extends PsychObject
{ {
/** /****************************************************************************
* Used to indicate to the ServerManager that all resources must be registered (and * Used to indicate to the ServerManager that all resources must be registered (and
* subsequently downloaded) * subsequently downloaded)
* *
@ -54,14 +54,14 @@ export class ServerManager extends PsychObject
this._addAttribute("status", ServerManager.Status.READY); this._addAttribute("status", ServerManager.Status.READY);
} }
/** /****************************************************************************
* @typedef ServerManager.GetConfigurationPromise * @typedef ServerManager.GetConfigurationPromise
* @property {string} origin the calling method * @property {string} origin the calling method
* @property {string} context the context * @property {string} context the context
* @property {Object.<string, *>} [config] the configuration * @property {Object.<string, *>} [config] the configuration
* @property {Object.<string, *>} [error] an error message if we could not read the configuration file * @property {Object.<string, *>} [error] an error message if we could not read the configuration file
*/ */
/** /****************************************************************************
* Read the configuration file for the experiment. * Read the configuration file for the experiment.
* *
* @name module:core.ServerManager#getConfiguration * @name module:core.ServerManager#getConfiguration
@ -100,14 +100,14 @@ export class ServerManager extends PsychObject
}); });
} }
/** /****************************************************************************
* @typedef ServerManager.OpenSessionPromise * @typedef ServerManager.OpenSessionPromise
* @property {string} origin the calling method * @property {string} origin the calling method
* @property {string} context the context * @property {string} context the context
* @property {string} [token] the session token * @property {string} [token] the session token
* @property {Object.<string, *>} [error] an error message if we could not open the session * @property {Object.<string, *>} [error] an error message if we could not open the session
*/ */
/** /****************************************************************************
* Open a session for this experiment on the remote PsychoJS manager. * Open a session for this experiment on the remote PsychoJS manager.
* *
* @name module:core.ServerManager#openSession * @name module:core.ServerManager#openSession
@ -190,13 +190,13 @@ export class ServerManager extends PsychObject
}); });
} }
/** /****************************************************************************
* @typedef ServerManager.CloseSessionPromise * @typedef ServerManager.CloseSessionPromise
* @property {string} origin the calling method * @property {string} origin the calling method
* @property {string} context the context * @property {string} context the context
* @property {Object.<string, *>} [error] an error message if we could not close the session (e.g. if it has not previously been opened) * @property {Object.<string, *>} [error] an error message if we could not close the session (e.g. if it has not previously been opened)
*/ */
/** /****************************************************************************
* Close the session for this experiment on the remote PsychoJS manager. * Close the session for this experiment on the remote PsychoJS manager.
* *
* @name module:core.ServerManager#closeSession * @name module:core.ServerManager#closeSession
@ -277,7 +277,7 @@ export class ServerManager extends PsychObject
} }
} }
/** /****************************************************************************
* Get the value of a resource. * Get the value of a resource.
* *
* @name module:core.ServerManager#getResource * @name module:core.ServerManager#getResource
@ -316,7 +316,7 @@ export class ServerManager extends PsychObject
return pathStatusData.data; return pathStatusData.data;
} }
/** /****************************************************************************
* Get the status of a resource. * Get the status of a resource.
* *
* @name module:core.ServerManager#getResourceStatus * @name module:core.ServerManager#getResourceStatus
@ -343,7 +343,7 @@ export class ServerManager extends PsychObject
return pathStatusData.status; return pathStatusData.status;
} }
/** /****************************************************************************
* Set the resource manager status. * Set the resource manager status.
* *
* @name module:core.ServerManager#setStatus * @name module:core.ServerManager#setStatus
@ -376,7 +376,7 @@ export class ServerManager extends PsychObject
return this._status; return this._status;
} }
/** /****************************************************************************
* Reset the resource manager status to ServerManager.Status.READY. * Reset the resource manager status to ServerManager.Status.READY.
* *
* @name module:core.ServerManager#resetStatus * @name module:core.ServerManager#resetStatus
@ -389,7 +389,7 @@ export class ServerManager extends PsychObject
return this.setStatus(ServerManager.Status.READY); return this.setStatus(ServerManager.Status.READY);
} }
/** /****************************************************************************
* Prepare resources for the experiment: register them with the server manager and possibly * Prepare resources for the experiment: register them with the server manager and possibly
* start downloading them right away. * start downloading them right away.
* *
@ -525,7 +525,7 @@ export class ServerManager extends PsychObject
} }
} }
/** /****************************************************************************
* Block the experiment until the specified resources have been downloaded. * Block the experiment until the specified resources have been downloaded.
* *
* @name module:core.ServerManager#waitForResources * @name module:core.ServerManager#waitForResources
@ -621,13 +621,13 @@ export class ServerManager extends PsychObject
}; };
} }
/** /****************************************************************************
* @typedef ServerManager.UploadDataPromise * @typedef ServerManager.UploadDataPromise
* @property {string} origin the calling method * @property {string} origin the calling method
* @property {string} context the context * @property {string} context the context
* @property {Object.<string, *>} [error] an error message if we could not upload the data * @property {Object.<string, *>} [error] an error message if we could not upload the data
*/ */
/** /****************************************************************************
* Asynchronously upload experiment data to the pavlovia server. * Asynchronously upload experiment data to the pavlovia server.
* *
* @name module:core.ServerManager#uploadData * @name module:core.ServerManager#uploadData
@ -692,7 +692,7 @@ export class ServerManager extends PsychObject
} }
} }
/** /****************************************************************************
* Asynchronously upload experiment logs to the pavlovia server. * Asynchronously upload experiment logs to the pavlovia server.
* *
* @name module:core.ServerManager#uploadLog * @name module:core.ServerManager#uploadLog
@ -751,37 +751,40 @@ export class ServerManager extends PsychObject
}); });
} }
/** /****************************************************************************
* Asynchronously upload audio data to the pavlovia server. * Synchronously or asynchronously upload audio data to the pavlovia server.
* *
* @name module:core.ServerManager#uploadAudioVideo * @name module:core.ServerManager#uploadAudioVideo
* @function * @function
* @public * @public
* @param {Blob} audioBlob - the audio blob to be uploaded * @param {Blob} mediaBlob - the audio or video blob to be uploaded
* @param {string} tag - additional tag * @param {string} tag - additional tag
* @param {boolean} [waitForCompletion=false] - whether or not to wait for completion
* before returning
* @returns {Promise<ServerManager.UploadDataPromise>} the response * @returns {Promise<ServerManager.UploadDataPromise>} the response
*/ */
async uploadAudioVideo(audioBlob, tag) async uploadAudioVideo(mediaBlob, tag, waitForCompletion = false)
{ {
const response = { const response = {
origin: "ServerManager.uploadAudio", origin: "ServerManager.uploadAudio",
context: "when uploading audio data for experiment: " + this._psychoJS.config.experiment.fullpath, context: "when uploading media data for experiment: " + this._psychoJS.config.experiment.fullpath,
}; };
try try
{ {
if ( if (this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER
this._psychoJS.getEnvironment() !== ExperimentHandler.Environment.SERVER
|| this._psychoJS.config.experiment.status !== "RUNNING" || this._psychoJS.config.experiment.status !== "RUNNING"
|| this._psychoJS._serverMsg.has("__pilotToken") || this._psychoJS._serverMsg.has("__pilotToken"))
)
{ {
throw "audio recordings can only be uploaded to the server for experiments running on the server"; throw "media recordings can only be uploaded to the server for experiments running on the server";
} }
this._psychoJS.logger.debug("uploading audio data for experiment: " + this._psychoJS.config.experiment.fullpath); this._psychoJS.logger.debug(`uploading media data for experiment: ${this._psychoJS.config.experiment.fullpath}`);
this.setStatus(ServerManager.Status.BUSY); this.setStatus(ServerManager.Status.BUSY);
// open pop-up dialog:
// TODO
// prepare the request: // prepare the request:
const info = this.psychoJS.experiment.extraInfo; const info = this.psychoJS.experiment.extraInfo;
const participant = ((typeof info.participant === "string" && info.participant.length > 0) ? info.participant : "PARTICIPANT"); const participant = ((typeof info.participant === "string" && info.participant.length > 0) ? info.participant : "PARTICIPANT");
@ -790,15 +793,15 @@ export class ServerManager extends PsychObject
const filename = participant + "_" + experimentName + "_" + datetime + "_" + tag; const filename = participant + "_" + experimentName + "_" + datetime + "_" + tag;
const formData = new FormData(); const formData = new FormData();
formData.append("audio", audioBlob, filename); formData.append("media", mediaBlob, filename);
const url = this._psychoJS.config.pavlovia.URL let url = this._psychoJS.config.pavlovia.URL
+ "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId + "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId
+ "/sessions/" + this._psychoJS.config.session.token + "/sessions/" + this._psychoJS.config.session.token
+ "/audio"; + "/media";
// query the pavlovia server: // query the server:
const response = await fetch(url, { let response = await fetch(url, {
method: "POST", method: "POST",
mode: "cors", mode: "cors",
cache: "no-cache", cache: "no-cache",
@ -807,16 +810,58 @@ export class ServerManager extends PsychObject
referrerPolicy: "no-referrer", referrerPolicy: "no-referrer",
body: formData, body: formData,
}); });
const jsonResponse = await response.json(); const postMediaResponse = await response.json();
this._psychoJS.logger.debug(`post media response: ${JSON.stringify(postMediaResponse)}`);
// deal with server errors: // deal with server errors:
if (!response.ok) if (!response.ok)
{ {
throw jsonResponse; throw postMediaResponse;
}
// wait until the upload has completed:
if (waitForCompletion)
{
if (!("uploadToken" in postMediaResponse))
{
throw "incorrect server response: missing uploadToken";
}
const uploadToken = postMediaResponse['uploadToken'];
while (true)
{
// wait a bit:
await new Promise(r =>
{
setTimeout(r, 1000);
});
// check the status of the upload:
url = this._psychoJS.config.pavlovia.URL
+ "/api/v2/experiments/" + this._psychoJS.config.gitlab.projectId
+ "/sessions/" + this._psychoJS.config.session.token
+ "/media/" + uploadToken + "/status";
response = await fetch(url, {
method: "GET",
mode: "cors",
cache: "no-cache",
credentials: "same-origin",
redirect: "follow",
referrerPolicy: "no-referrer"
});
const checkSatusResponse = await response.json();
this._psychoJS.logger.debug(`check upload status response: ${JSON.stringify(checkStatusResponse)}`);
if (("status" in checkStatusResponse) && checkStatusResponse["status"] === "COMPLETED")
{
break;
}
}
} }
this.setStatus(ServerManager.Status.READY); this.setStatus(ServerManager.Status.READY);
return jsonResponse; return postMediaResponse;
} }
catch (error) catch (error)
{ {
@ -827,9 +872,9 @@ export class ServerManager extends PsychObject
} }
} }
/** /****************************************************************************
* List the resources available to the experiment. * List the resources available to the experiment.
*
* @name module:core.ServerManager#_listResources * @name module:core.ServerManager#_listResources
* @function * @function
* @private * @private
@ -896,7 +941,7 @@ export class ServerManager extends PsychObject
}); });
} }
/** /****************************************************************************
* Download the specified resources. * Download the specified resources.
* *
* <p>Note: we use the [preloadjs library]{@link https://www.createjs.com/preloadjs}.</p> * <p>Note: we use the [preloadjs library]{@link https://www.createjs.com/preloadjs}.</p>
@ -1124,7 +1169,7 @@ export class ServerManager extends PsychObject
} }
} }
/** /****************************************************************************
* Server event * Server event
* *
* <p>A server event is emitted by the manager to inform its listeners of either a change of status, or of a resource related event (e.g. download started, download is completed).</p> * <p>A server event is emitted by the manager to inform its listeners of either a change of status, or of a resource related event (e.g. download started, download is completed).</p>
@ -1166,7 +1211,7 @@ ServerManager.Event = {
STATUS: Symbol.for("STATUS"), STATUS: Symbol.for("STATUS"),
}; };
/** /****************************************************************************
* Server status * Server status
* *
* @name module:core.ServerManager#Status * @name module:core.ServerManager#Status
@ -1191,7 +1236,7 @@ ServerManager.Status = {
ERROR: Symbol.for("ERROR"), ERROR: Symbol.for("ERROR"),
}; };
/** /****************************************************************************
* Resource status * Resource status
* *
* @name module:core.ServerManager#ResourceStatus * @name module:core.ServerManager#ResourceStatus

View File

@ -369,9 +369,12 @@ export class Camera extends PsychObject
* @name module:visual.Camera#upload * @name module:visual.Camera#upload
* @function * @function
* @public * @public
* @param {string} tag an optional tag for the audio file * @param {Object} options
* @param {string} options.tag an optional tag for the video file
* @param {boolean} [options.waitForCompletion= false] whether or not to wait for completion
* before returning
*/ */
async upload({tag} = {}) async upload({ tag, waitForCompletion = false } = {})
{ {
// default tag: the name of this Camera object // default tag: the name of this Camera object
if (typeof tag === "undefined") if (typeof tag === "undefined")
@ -394,7 +397,7 @@ export class Camera extends PsychObject
// upload the blob: // upload the blob:
const videoBlob = new Blob(this._videoBuffer); const videoBlob = new Blob(this._videoBuffer);
return this._psychoJS.serverManager.uploadAudioVideo(videoBlob, tag); return this._psychoJS.serverManager.uploadAudioVideo(videoBlob, tag, waitForCompletion);
} }