Index: openacs-4/packages/proctoring-support/lib/proctored-page.adp
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/lib/proctored-page.adp,v
diff -u -r1.1.2.10 -r1.1.2.11
--- openacs-4/packages/proctoring-support/lib/proctored-page.adp 6 Oct 2020 17:09:13 -0000 1.1.2.10
+++ openacs-4/packages/proctoring-support/lib/proctored-page.adp 22 Oct 2020 14:22:20 -0000 1.1.2.11
@@ -29,21 +29,27 @@
-
-
#proctoring-support.grant_access_to_microphone_title#
-
#proctoring-support.grant_access_to_microphone_msg#
-
-
-
-
#proctoring-support.grant_access_to_camera_title#
-
#proctoring-support.grant_access_to_camera_msg#
-
-
-
-
#proctoring-support.grant_access_to_desktop_title#
-
#proctoring-support.grant_access_to_desktop_msg#
-
-
+
+
+
#proctoring-support.grant_access_to_microphone_title#
+
#proctoring-support.grant_access_to_microphone_msg#
+
+
+
+
+
+
#proctoring-support.grant_access_to_camera_title#
+
#proctoring-support.grant_access_to_camera_msg#
+
+
+
+
+
+
#proctoring-support.grant_access_to_desktop_title#
+
#proctoring-support.grant_access_to_desktop_msg#
+
+
+
@@ -64,10 +70,16 @@
+
+
+
+
+
+
+
+
+
-
-
-
@@ -90,6 +102,8 @@
var examinationStatementURL = "@examination_statement_url;literal@";
var hasPreview = @preview_p;literal@;
var hasProctoring = @proctoring_p;literal@;
+ var hasCamera = @camera_p;literal@;
+ var hasDesktop = @desktop_p;literal@;
var hasAudio = @audio_p;literal@;
var minMsInterval = @min_ms_interval;literal@;
var maxMsInterval = @max_ms_interval;literal@;
Index: openacs-4/packages/proctoring-support/lib/proctored-page.tcl
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/lib/proctored-page.tcl,v
diff -u -r1.1.2.5 -r1.1.2.6
--- openacs-4/packages/proctoring-support/lib/proctored-page.tcl 23 Sep 2020 17:23:29 -0000 1.1.2.5
+++ openacs-4/packages/proctoring-support/lib/proctored-page.tcl 22 Oct 2020 14:22:20 -0000 1.1.2.6
@@ -37,6 +37,10 @@
be displayed to users during proctored session
@param proctoring_p Do the actual proctoring. Can be disabled to
display just the exmaination statement
+ @param camera_p proctor the camera. If false, camera will not be
+ recorded.
+ @param desktop_p proctor the desktop screen. If false, desktop
+ screen will not be recorded.
@param examination_statement_p Display the examination statement
@param examination_statement_url URL we are calling in order to
store acceptance of the examination statement. It will
@@ -70,6 +74,8 @@
{max_audio_duration:naturalnum 60}
{preview_p:boolean false}
{proctoring_p:boolean true}
+ {camera_p:boolean true}
+ {desktop_p:boolean true}
{check_active_p:boolean true}
{examination_statement_p:boolean true}
{examination_statement_url:localurl "/proctoring/examination-statement-accept"}
@@ -113,5 +119,9 @@
set mobile_p [ad_conn mobile_p]
set check_active_p [expr {$check_active_p ? true : false}]
set preview_p [expr {$preview_p ? true : false}]
-set proctoring_p [expr {$proctoring_p ? true : false}]
+set proctoring_p [expr {$proctoring_p &&
+ ($camera_p || $audio_p || $desktop_p) ? true : false}]
set upload_p [expr {$upload_p ? true : false}]
+set audio_p [expr {$audio_p ? true : false}]
+set camera_p [expr {$camera_p ? true : false}]
+set desktop_p [expr {$desktop_p ? true : false}]
Index: openacs-4/packages/proctoring-support/www/resources/proctored-page.js
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/www/resources/proctored-page.js,v
diff -u -r1.1.2.15 -r1.1.2.16
--- openacs-4/packages/proctoring-support/www/resources/proctored-page.js 8 Oct 2020 14:04:10 -0000 1.1.2.15
+++ openacs-4/packages/proctoring-support/www/resources/proctored-page.js 22 Oct 2020 14:22:20 -0000 1.1.2.16
@@ -71,18 +71,24 @@
var e = document.querySelector("#preview-placeholder");
style = !hasPreview ? "position:absolute;top:0;left:0;" : "";
e.setAttribute("style", style);
- var canvas = document.createElement("canvas");
- style = hasPreview ? "height: 30px; width: 40px" : "height: 1px; width: 1px";
- canvas.setAttribute("style", style);
- canvas.setAttribute("id", "audio-preview");
- e.appendChild(canvas);
- new AudioWave(proctoring.streams[0], "#audio-preview");
- for (var i = 0; i < proctoring.videos.length; i++) {
- var video = proctoring.videos[i];
- var width = hasPreview ? 30 : 1;
- video.setAttribute("height", width);
- e.appendChild(video);
+ for (stream of proctoring.streams) {
+ if (stream && stream.getAudioTracks().length > 0) {
+ var canvas = document.createElement("canvas");
+ style = hasPreview ? "height: 30px; width: 40px" : "height: 1px; width: 1px";
+ canvas.setAttribute("style", style);
+ canvas.setAttribute("id", "audio-preview");
+ e.appendChild(canvas);
+ new AudioWave(stream, "#audio-preview");
+ break;
+ }
}
+ for (video of proctoring.videos) {
+ if (video) {
+ var width = hasPreview ? 30 : 1;
+ video.setAttribute("height", width);
+ e.appendChild(video);
+ }
+ }
}
var uploadHandle = null;
@@ -222,100 +228,105 @@
setError(errmsg);
}
});
- handlers.push(function () {
- valid = false;
- clearError();
- var constraints = {
- audio: cameraConstraints.audio
- };
- navigator.mediaDevices.getUserMedia(constraints).then(stream => {
- if (streamMuted(stream)) {
- throw yourMicrophoneIsMutedMessage;
- } else {
+ if (hasAudio) {
+ handlers.push(function () {
+ valid = false;
+ clearError();
+ var constraints = {
+ audio: cameraConstraints.audio
+ };
+ navigator.mediaDevices.getUserMedia(constraints).then(stream => {
+ if (streamMuted(stream)) {
+ throw yourMicrophoneIsMutedMessage;
+ } else {
+ new AudioWave(stream, "#audio");
+ valid = true;
+ streams[0] = stream;
+ }
+ }).catch(err => {
+ if (err.name == "NotAllowedError") {
+ err = microphonePermissionDeniedMessage;
+ } else if (err.name == "NotFoundError") {
+ err = microphoneNotFoundMessage;
+ } else if (err.name == "NotReadableError") {
+ err = microphoneNotReadableMessage;
+ }
+ setError(err);
+ });
+ });
+ }
+ if (hasCamera) {
+ handlers.push(function () {
+ valid = false;
+ clearError();
+ var constraints = {
+ video: cameraConstraints.video
+ };
+ navigator.mediaDevices.getUserMedia(constraints).then(stream => {
camvideo.srcObject = stream;
- new AudioWave(stream, "#audio");
+ camvideo.style.display = "block";
+ streams[1] = stream;
+ camvideo.addEventListener("play", function() {
+ var canvas = document.createElement("canvas");
+ canvas.width = camvideo.videoWidth;
+ canvas.height = camvideo.videoHeight;
+ canvas.getContext("2d").drawImage(camvideo, 0, 0, camvideo.videoWidth, camvideo.videoHeight);
+ canvas.toBlob(function(blob) {
+ if (blob == null ||
+ blob.size <= blackPictureSizeThreshold) {
+ var errmsg = blackPictureCameraMessage;
+ setError(errmsg);
+ }
+ }, "image/jpeg");
+ });
valid = true;
- streams[0] = stream;
- }
- }).catch(err => {
- if (err.name == "NotAllowedError") {
- err = microphonePermissionDeniedMessage;
- } else if (err.name == "NotFoundError") {
- err = microphoneNotFoundMessage;
- } else if (err.name == "NotReadableError") {
- err = microphoneNotReadableMessage;
- }
- setError(err);
- });
- });
- handlers.push(function () {
- valid = false;
- clearError();
- var constraints = {
- video: cameraConstraints.video
- };
- navigator.mediaDevices.getUserMedia(constraints).then(stream => {
- camvideo.srcObject = stream;
- camvideo.style.display = "block";
- streams[1] = stream;
- camvideo.addEventListener("play", function() {
- var canvas = document.createElement("canvas");
- canvas.width = camvideo.videoWidth;
- canvas.height = camvideo.videoHeight;
- canvas.getContext("2d").drawImage(camvideo, 0, 0, camvideo.videoWidth, camvideo.videoHeight);
- canvas.toBlob(function(blob) {
- if (blob == null ||
- blob.size <= blackPictureSizeThreshold) {
- var errmsg = blackPictureCameraMessage;
- setError(errmsg);
- }
- }, "image/jpeg");
+ }).catch(err => {
+ if (err.name == "NotAllowedError") {
+ err = cameraPermissionDeniedMessage;
+ } else if (err.name == "NotFoundError") {
+ err = cameraNotFoundMessage;
+ } else if (err.name == "NotReadableError") {
+ err = cameraNotReadableMessage;
+ }
+ setError(err);
});
- valid = true;
- }).catch(err => {
- if (err.name == "NotAllowedError") {
- err = cameraPermissionDeniedMessage;
- } else if (err.name == "NotFoundError") {
- err = cameraNotFoundMessage;
- } else if (err.name == "NotReadableError") {
- err = cameraNotReadableMessage;
- }
- setError(err);
});
- });
- handlers.push(function () {
- valid = false;
- clearError();
- var constraints = {
- video: desktopConstraints.video
- };
- navigator.mediaDevices.getDisplayMedia(constraints).then(stream=> {
- var requestedStream = constraints.video.displaySurface;
- var selectedStream = stream.getVideoTracks()[0].getSettings().displaySurface;
- // If user requested for a specific displaysurface
- // and browser supports it, also check that the
- // one selected is right.
- if (requestedStream == undefined ||
- (selectedStream != undefined &&
- requestedStream == selectedStream)) {
- deskvideo.srcObject = stream;
- deskvideo.style.display = "block";
- valid = true;
- streams[2] = stream;
- } else {
- if (selectedStream != undefined) {
- throw wrongDisplaySurfaceSelectedMessage;
+ }
+ if (hasDesktop) {
+ handlers.push(function () {
+ valid = false;
+ clearError();
+ var constraints = {
+ video: desktopConstraints.video
+ };
+ navigator.mediaDevices.getDisplayMedia(constraints).then(stream=> {
+ var requestedStream = constraints.video.displaySurface;
+ var selectedStream = stream.getVideoTracks()[0].getSettings().displaySurface;
+ // If user requested for a specific displaysurface
+ // and browser supports it, also check that the
+ // one selected is right.
+ if (requestedStream == undefined ||
+ (selectedStream != undefined &&
+ requestedStream == selectedStream)) {
+ deskvideo.srcObject = stream;
+ deskvideo.style.display = "block";
+ valid = true;
+ streams[2] = stream;
} else {
- throw displaySurfaceNotSupportedMessage;
+ if (selectedStream != undefined) {
+ throw wrongDisplaySurfaceSelectedMessage;
+ } else {
+ throw displaySurfaceNotSupportedMessage;
+ }
}
- }
- }).catch(err => {
- if (err.name == "NotAllowedError") {
- err = desktopPermissionDeniedMessage;
- }
- setError(err);
+ }).catch(err => {
+ if (err.name == "NotAllowedError") {
+ err = desktopPermissionDeniedMessage;
+ }
+ setError(err);
+ });
});
- });
+ }
}
if (hasExaminationStatement) {
handlers.push(function () {
@@ -332,6 +343,7 @@
function showTab(n) {
// This function will display the specified tab of the form...
var x = document.getElementsByClassName("tab");
+ if (x.length == 0) return;
x[n].style.display = "block";
//... and fix the Previous/Next buttons:
if (n == 0) {
@@ -442,8 +454,48 @@
document.querySelector("#wizard").style.display = "none";
document.querySelector("#proctoring").style.display = "block";
- var cameraStream = embedAudioTrackFromStream(streams[0], streams[1]);
+ var mediaConf = {};
+ var cameraStream;
+ if (hasAudio && hasCamera) {
+ cameraStream = embedAudioTrackFromStream(streams[0], streams[1]);
+ } else if (hasAudio) {
+ cameraStream = streams[0];
+ } else if (hasCamera) {
+ cameraStream = streams[1];
+ }
+ if (hasAudio || hasCamera) {
+ mediaConf.camera = {
+ required: true,
+ grayscale: true,
+ width: 320,
+ height: 240,
+ imageHandlers: {
+ jpeg: {
+ blob: function(blob) {
+ scheduleUpload("camera", "image", blob);
+ }
+ }
+ },
+ audioHandlers: audioHandlers,
+ stream: cameraStream
+ };
+ }
var desktopStream = streams[2];
+ if (hasDesktop) {
+ mediaConf.desktop = {
+ required: true,
+ grayscale: false,
+ imageHandlers: {
+ jpeg: {
+ blob: function(blob) {
+ scheduleUpload("desktop", "image", blob);
+ }
+ }
+ },
+ stream: desktopStream
+ };
+ }
+
var conf = {
minMsInterval: minMsInterval,
maxMsInterval: maxMsInterval,
@@ -462,35 +514,7 @@
createIframe();
createPreview();
},
- mediaConf: {
- camera: {
- required: true,
- grayscale: true,
- width: 320,
- height: 240,
- imageHandlers: {
- jpeg: {
- blob: function(blob) {
- scheduleUpload("camera", "image", blob);
- }
- }
- },
- audioHandlers: audioHandlers,
- stream: cameraStream
- },
- desktop: {
- required: true,
- grayscale: false,
- imageHandlers: {
- jpeg: {
- blob: function(blob) {
- scheduleUpload("desktop", "image", blob);
- }
- }
- },
- stream: desktopStream
- }
- }
+ mediaConf: mediaConf
};
if (hasProctoring) {
Index: openacs-4/packages/proctoring-support/www/resources/proctoring.js
===================================================================
RCS file: /usr/local/cvsroot/openacs-4/packages/proctoring-support/www/resources/proctoring.js,v
diff -u -r1.1.2.6 -r1.1.2.7
--- openacs-4/packages/proctoring-support/www/resources/proctoring.js 7 Oct 2020 10:12:31 -0000 1.1.2.6
+++ openacs-4/packages/proctoring-support/www/resources/proctoring.js 22 Oct 2020 14:22:20 -0000 1.1.2.7
@@ -398,7 +398,9 @@
this.maxAudioDuration).start();
}
this.streams[i] = stream;
- this.videos[i] = this.createVideo(stream);
+ if (stream.getVideoTracks().length > 0) {
+ this.videos[i] = this.createVideo(stream);
+ }
this.numActiveStreams++;
this.numCheckedStreams++;
}
@@ -529,7 +531,7 @@
throw "stream is not active";
} else if (this.streamMuted(stream)) {
throw "stream is muted";
- } else if (this.ready && video.paused) {
+ } else if (this.ready && video && video.paused) {
throw "video acquisition appears to have stopped";
}
} catch (e) {
@@ -794,9 +796,9 @@
}
this.ready = true;
}
- // For every configured stream, take a picture
+ // For every configured video stream, take a picture
for (var i = 0; i < this.streams.length; i++) {
- if (this.streams[i] != null) {
+ if (this.videos[i]) {
this.takeShot(this.streams[i], this.mediaConf[this.streamNames[i]].grayscale);
}
}