proctoring 2.0.2

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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +245 -0
  4. data/Rakefile +32 -0
  5. data/app/assets/config/100ms_manifest.js +1 -0
  6. data/app/assets/config/knights_watch_manifest.js +4 -0
  7. data/app/assets/config/kurento_manifest.js +1 -0
  8. data/app/assets/config/proctoring_manifest.js +3 -0
  9. data/app/assets/config/videojs_manifest.js +2 -0
  10. data/app/assets/images/proctoring/poster.png +0 -0
  11. data/app/assets/javascripts/100ms/examine.js +27 -0
  12. data/app/assets/javascripts/100ms/hundred_ms.js +143 -0
  13. data/app/assets/javascripts/100ms/join_proctor_room.js +17 -0
  14. data/app/assets/javascripts/100ms/proctor.js +152 -0
  15. data/app/assets/javascripts/kurento/LiveVideoUsingSignalingServer.js +344 -0
  16. data/app/assets/javascripts/kurento/VideoPlayer.js +63 -0
  17. data/app/assets/javascripts/kurento/VideoRecording.js +286 -0
  18. data/app/assets/javascripts/kurento/VideoRecordingUsingSignalingServer.js +224 -0
  19. data/app/assets/javascripts/kurento/co.js +299 -0
  20. data/app/assets/javascripts/kurento/kurento-utils.js +4418 -0
  21. data/app/assets/javascripts/proctoring/stream_channel.js +66 -0
  22. data/app/assets/javascripts/proctoring/stream_room.js +131 -0
  23. data/app/assets/javascripts/proctoring/video_recorder.js +172 -0
  24. data/app/assets/javascripts/videojs/videojs-playlist-ui.js +516 -0
  25. data/app/assets/javascripts/videojs/videojs-playlist.js +909 -0
  26. data/app/assets/stylesheets/proctoring/application.css +15 -0
  27. data/app/assets/stylesheets/proctoring/video_player_100ms.css +34 -0
  28. data/app/assets/stylesheets/proctoring/video_streamings.css +49 -0
  29. data/app/assets/stylesheets/scaffold.css +80 -0
  30. data/app/assets/stylesheets/videojs/videojs-playlist-ui.css +1 -0
  31. data/app/controllers/proctoring/api/v1/authentication_controller.rb +31 -0
  32. data/app/controllers/proctoring/api/v1/hundred_ms/services_controller.rb +24 -0
  33. data/app/controllers/proctoring/application_controller.rb +23 -0
  34. data/app/controllers/proctoring/video_streamings_controller.rb +108 -0
  35. data/app/helpers/proctoring/application_helper.rb +4 -0
  36. data/app/helpers/proctoring/hundred_ms_service_helper.rb +90 -0
  37. data/app/helpers/proctoring/tokens_helper.rb +55 -0
  38. data/app/helpers/proctoring/video_streamings_helper.rb +4 -0
  39. data/app/jobs/proctoring/application_job.rb +4 -0
  40. data/app/mailers/proctoring/application_mailer.rb +6 -0
  41. data/app/models/proctoring/application_record.rb +5 -0
  42. data/app/models/proctoring/video_streaming.rb +54 -0
  43. data/app/models/proctoring/video_streaming_room.rb +29 -0
  44. data/app/views/layouts/proctoring/application.html.erb +15 -0
  45. data/app/views/proctoring/video_player/live_video_proctoring.html.erb +84 -0
  46. data/app/views/proctoring/video_player/live_video_proctoring_100ms.html.erb +48 -0
  47. data/app/views/proctoring/video_player/video_player.html.erb +40 -0
  48. data/app/views/proctoring/video_streamings/_form.html.erb +27 -0
  49. data/app/views/proctoring/video_streamings/_list.html.erb +27 -0
  50. data/app/views/proctoring/video_streamings/_record_video_from_client.html.erb +8 -0
  51. data/app/views/proctoring/video_streamings/_socket_rtc_scripts.html.erb +5 -0
  52. data/app/views/proctoring/video_streamings/_stream_video.html.erb +8 -0
  53. data/app/views/proctoring/video_streamings/_video_recording.html.erb +3 -0
  54. data/app/views/proctoring/video_streamings/_video_recording100ms.html.erb +4 -0
  55. data/app/views/proctoring/video_streamings/distribute_channel_to_rooms.html.erb +39 -0
  56. data/app/views/proctoring/video_streamings/edit.html.erb +6 -0
  57. data/app/views/proctoring/video_streamings/event.html.erb +9 -0
  58. data/app/views/proctoring/video_streamings/index.html.erb +1 -0
  59. data/app/views/proctoring/video_streamings/new.html.erb +5 -0
  60. data/app/views/proctoring/video_streamings/show.html.erb +19 -0
  61. data/app/views/proctoring/video_streamings/stream_channel.html.erb +1 -0
  62. data/app/views/proctoring/video_streamings/stream_room.html.erb +1 -0
  63. data/config/routes.rb +24 -0
  64. data/db/migrate/20200526061313_create_proctoring_video_streamings.rb +15 -0
  65. data/db/migrate/20200527045158_create_proctoring_video_streaming_rooms.rb +13 -0
  66. data/lib/proctoring/engine.rb +41 -0
  67. data/lib/proctoring/version.rb +3 -0
  68. data/lib/proctoring.rb +5 -0
  69. data/lib/tasks/proctoring_tasks.rake +4 -0
  70. metadata +158 -0
@@ -0,0 +1,66 @@
1
+ let channelElm = document.getElementById('channel-data');
2
+ navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia || navigator.mediaDevices.getUserMedia;
3
+ let connection = new RTCMultiConnection();
4
+ connection.socketURL = channelElm.dataset.mediaServerUrl;
5
+
6
+ connection.session = {
7
+ audio: false,
8
+ video: true
9
+ };
10
+
11
+ connection.sdpConstraints.mandatory = {
12
+ OfferToReceiveAudio: false,
13
+ OfferToReceiveVideo: true
14
+ };
15
+
16
+ connection.extra = {
17
+ userType: 'proctor',
18
+ };
19
+
20
+ connection.onstream = function(event) {
21
+ const video = event.mediaElement;
22
+ if(event && event.userid) {
23
+ if(event.extra && event.extra.userType === 'proctor') {
24
+ return;
25
+ }
26
+ video.id = event.userid;
27
+ const videoContainer = document.getElementById('video-container');
28
+ const userVideoElm = document.getElementById(event.userid);
29
+ if(userVideoElm) {
30
+ userVideoElm.parentNode.removeChild(userVideoElm);
31
+ }
32
+ videoContainer.querySelector(".video-div").prepend(video);
33
+ const statusElm = videoContainer.querySelector('.video-status');
34
+ statusElm.innerHTML = `Status: ${event.status}`;
35
+ }
36
+ };
37
+ connection.onUserStatusChanged = function (event) {
38
+ const userVideoElm = document.getElementById(event.userid);
39
+ if (userVideoElm && event && event.status) {
40
+ userVideoElm.parentElement.style.backgroundColor =
41
+ event.status === "online" ? "#34A853" : "#EA4335";
42
+ const status = document.getElementById(`video-status-${event.userid}`);
43
+ if (status && event.status) status.innerHTML = `Status: ${event.status}`;
44
+ }
45
+ }
46
+
47
+ connection.onstreamended = function(event) {
48
+ let video = document.getElementById(event.userid);
49
+ if (video && video.parentNode) {
50
+ video.parentNode.removeChild(video);
51
+ }
52
+ };
53
+
54
+
55
+ let url = new URL(window.location);
56
+ let channel = channelElm.dataset.channel;
57
+ if (channel) {
58
+ connection.dontCaptureUserMedia = true;
59
+ navigator.getUserMedia({
60
+ video: true,
61
+ audio: false
62
+ }, function(externalStream) {
63
+ connection.attachStreams = [externalStream];
64
+ connection.openOrJoin(channel);
65
+ }, function(error) {});
66
+ }
@@ -0,0 +1,131 @@
1
+ navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia || navigator.mediaDevices.getUserMedia;
2
+ const channelDiv = document.getElementById("channel-data");
3
+ let connection = new RTCMultiConnection();
4
+ connection.socketURL = `https://${channelDiv.dataset.mediaServerUrl}/`;
5
+ var listOfRecorders = {};
6
+
7
+ connection.session = {
8
+ audio: false,
9
+ video: true
10
+ };
11
+
12
+ connection.sdpConstraints.mandatory = {
13
+ OfferToReceiveAudio: false,
14
+ OfferToReceiveVideo: true
15
+ };
16
+
17
+ // connection.setUserPreferences = function(userPreferences) {
18
+ // userPreferences.dontAttachLocalStream = true;
19
+ // return userPreferences;
20
+ // };
21
+
22
+ connection.onstream = function(event) {
23
+ if(event && event.userid) {
24
+ if (event.extra && event.extra.userType === "proctor") {
25
+ return;
26
+ }
27
+ const userVideoElm = document.getElementById(event.userid);
28
+ if (userVideoElm) {
29
+ userVideoElm.parentNode.removeChild(userVideoElm);
30
+ }
31
+ const videoContainer = document.getElementById('video-container');
32
+ let div = document.createElement('div');
33
+ div.id = event.userid;
34
+ div.className = 'video-div';
35
+ div.appendChild(event.mediaElement); // appending VIDOE to DIV
36
+
37
+ let h2 = document.createElement('h4');
38
+ h2.innerHTML = `UserID: ${event.extra.userId}`;
39
+ div.appendChild(h2);
40
+
41
+ let pElm = document.createElement('p');
42
+ pElm.id = `video-status-${event.userid}`;
43
+ pElm.innerHTML = `Status: ${event.status}`;
44
+ div.appendChild(pElm);
45
+
46
+ let pElm2 = document.createElement('p');
47
+ pElm2.id = `video-recording-status-${event.userid}`;
48
+ pElm2.innerHTML = `Status: Recording...`;
49
+ div.appendChild(pElm2);
50
+
51
+ let button = document.createElement('button');
52
+ button.id = `video-recording-stop-${event.userid}`;
53
+ button.dataset = { userid: event.userid };
54
+ button.style.color = '#000';
55
+ button.innerHTML = `Stop Recording`;
56
+ div.appendChild(button);
57
+
58
+ div.style.backgroundColor = '#34A853';
59
+ div.style.textAlign = 'center';
60
+ div.style.color = '#fff';
61
+
62
+ videoContainer.appendChild(div);
63
+
64
+
65
+ /** Recording **/
66
+ let recorder = RecordRTC(event.stream, {
67
+ type: 'video',
68
+ recorderType: MediaStreamRecorder
69
+ });
70
+
71
+ recorder.startRecording();
72
+
73
+ listOfRecorders[event.userid] = recorder;
74
+
75
+ console.log(button);
76
+
77
+ button.addEventListener("click", function(){
78
+ let streamid = event.userid;
79
+
80
+ if(!listOfRecorders[streamid]) throw 'Wrong stream-id';
81
+
82
+ let recorder = listOfRecorders[streamid];
83
+ recorder.stopRecording(function() {
84
+ let blob = recorder.getBlob();
85
+ invokeSaveAsDialog(blob);
86
+ // window.open( URL.createObjectURL(blob) );
87
+ // or upload to server
88
+ // var formData = new FormData();
89
+ // formData.append('file', blob);
90
+ // $.post('/server-address', formData, serverCallback);
91
+ });
92
+ });
93
+ }
94
+ };
95
+ connection.onUserStatusChanged = function (event) {
96
+ const userVideoElm = document.getElementById(event.userid);
97
+ if(userVideoElm && event && event.status) {
98
+ userVideoElm.style.backgroundColor = event.status === 'online' ? '#34A853' : '#EA4335';
99
+ const status = document.getElementById(`video-status-${event.userid}`);
100
+ if(status && event.status) status.innerHTML = `Status: ${event.status}`;
101
+ }
102
+ }
103
+
104
+ connection.onstreamended = function(event) {
105
+ let video = document.getElementById(event.userid);
106
+ if (video && video.parentNode) {
107
+ video.parentNode.removeChild(video);
108
+ }
109
+ };
110
+
111
+ const connectVideos = () => {
112
+ setTimeout(function() {
113
+ if(channels.length) {
114
+ const channel = channels.shift()
115
+ navigator.getUserMedia({
116
+ video: true,
117
+ audio: false
118
+ }, function(externalStream) {
119
+ connection.attachStreams = [externalStream];
120
+ connection.openOrJoin(channel);
121
+ }, function(error) {});
122
+ connectVideos();
123
+ }
124
+ }, 3000)
125
+ }
126
+
127
+
128
+ let channels = [];
129
+ channels = JSON.parse(channelDiv.dataset.streamChannels);
130
+ connection.dontCaptureUserMedia = true;
131
+ connectVideos()
@@ -0,0 +1,172 @@
1
+ class VideoRecorder {
2
+ constructor(eventId, userId) {
3
+ this.eventId = eventId;
4
+ this.userId = userId;
5
+ let videoRecordingInfoElm = document.getElementById('video-recording-info');
6
+ this.appName = videoRecordingInfoElm.dataset.appName;
7
+ this.recordEndpoint = videoRecordingInfoElm.dataset.recordEndpoint;
8
+ this.uploadUrl = videoRecordingInfoElm.dataset.uploadUrl;
9
+ this.proctorResp = {};
10
+ this.listOfRecorders = {};
11
+ }
12
+
13
+ connectVideo() {
14
+ const { eventId, userId } = this;
15
+ fetch(
16
+ `/proctoring/video_streamings/user_channel.json?event_id=${eventId}&user_id=${userId}`
17
+ )
18
+ .then((response) => response.json())
19
+ .then((data) => {
20
+ this.proctorResp = data;
21
+ this.startWebrtcAndRecord();
22
+ });
23
+
24
+ }
25
+
26
+ startWebrtcAndRecord() {
27
+ const {
28
+ eventId,
29
+ userId,
30
+ proctorResp,
31
+ appName,
32
+ listOfRecorders,
33
+ recordEndpoint,
34
+ uploadUrl,
35
+ } = this;
36
+ const userConferenceId = `proctoring-video_streaming-${appName}-${eventId}-${userId}`;
37
+ const connection = new RTCMultiConnection();
38
+ window.listOfRecorders = listOfRecorders;
39
+
40
+ connection.socketURL = proctorResp.socketURL;
41
+ connection.session = {
42
+ audio: false,
43
+ video: true,
44
+ };
45
+
46
+ connection.sdpConstraints.mandatory = {
47
+ OfferToReceiveAudio: false,
48
+ OfferToReceiveVideo: true,
49
+ };
50
+
51
+ connection.userid = userConferenceId;
52
+
53
+ connection.extra = {
54
+ eventId,
55
+ userId,
56
+ };
57
+
58
+ const supports = navigator.mediaDevices.getSupportedConstraints();
59
+ if (supports.width && supports.height) {
60
+ connection.applyConstraints({
61
+ video: {
62
+ width: 320,
63
+ height: 240,
64
+ },
65
+ });
66
+ }
67
+
68
+ const resolutions = "VGA";
69
+ let videoConstraints = {};
70
+
71
+ if (resolutions === "VGA") {
72
+ videoConstraints = {
73
+ width: { min: 320, ideal: 320 },
74
+ height: { min: 240 },
75
+ frameRate: 15,
76
+ facingMode: "user",
77
+ };
78
+ }
79
+
80
+ connection.mediaConstraints = {
81
+ video: videoConstraints,
82
+ audio: true,
83
+ };
84
+
85
+ connection.setUserPreferences = (userPreferences) => {
86
+ // eslint-disable-next-line no-param-reassign
87
+ userPreferences.dontGetRemoteStream = true;
88
+ return userPreferences;
89
+ };
90
+
91
+ const recordAndUploadVideo = (event) => {
92
+ // eslint-disable-next-line no-undef
93
+ const recorder = RecordRTC(event.stream, {
94
+ type: "video",
95
+ // eslint-disable-next-line no-undef
96
+ // recorderType: MediaStreamRecorder,
97
+ timeSlice: 1000 * 60 * 1,
98
+ canvas: {
99
+ width: 320,
100
+ height: 240,
101
+ },
102
+ frameRate: 5,
103
+ mimeType: "video/x-matroska;codecs=avc1",
104
+ ondataavailable(blob) {
105
+ const fileOfBlob = new File(
106
+ [blob],
107
+ `videorecording-${appName}-${eventId}-${userId}-${+new Date()}.webm`,
108
+ { type: "video/webm" }
109
+ );
110
+ console.log(blob, fileOfBlob);
111
+ invokeSaveAsDialog(blob);
112
+ // const upload = new ActiveStorage.DirectUpload(
113
+ // fileOfBlob,
114
+ // uploadUrl,
115
+ // this
116
+ // );
117
+ // upload.create((error, blob) => {
118
+ // if (error) {
119
+ // console.log(error);
120
+ // } else {
121
+ // console.log(blob);
122
+
123
+ // const csrfElement = document.getElementsByName("csrf-token")[0];
124
+ // const csrfToken = csrfElement ? csrfElement.content : "";
125
+
126
+ // const submitData = {
127
+ // video_streaming: {
128
+ // videos: [blob.signed_id],
129
+ // user_id: userId,
130
+ // },
131
+ // };
132
+
133
+ // console.log(submitData);
134
+
135
+ // // fetch(`${recordEndpoint}/${proctorResp.id}/upload_video`, {
136
+ // // method: "POST",
137
+ // // headers: {
138
+ // // "Content-Type": "application/json",
139
+ // // "X-Requested-With": "XMLHttpRequest",
140
+ // // "X-CSRF-TOKEN": csrfToken,
141
+ // // Accept: "application/json",
142
+ // // },
143
+ // // body: JSON.stringify(submitData),
144
+ // // credentials: "include",
145
+ // // })
146
+ // // .then((res) => res.json())
147
+ // // .then((res) => console.log(res));
148
+ // }
149
+ // });
150
+ },
151
+ });
152
+
153
+ recorder.startRecording();
154
+
155
+ listOfRecorders[event.userid] = recorder;
156
+ };
157
+
158
+ connection.onstream = (event) => {
159
+ recordAndUploadVideo(event);
160
+ };
161
+
162
+ if (proctorResp && proctorResp.channel) {
163
+ console.log(proctorResp);
164
+ connection.openOrJoin(proctorResp.channel, () => {
165
+ setTimeout(() => {
166
+ const localStream = connection.attachStreams[0];
167
+ localStream.mute("audio");
168
+ }, 500);
169
+ });
170
+ }
171
+ }
172
+ }