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,224 @@
1
+ function videoRecordingUsingSignalingServer(props) {
2
+ // variables
3
+ let roomName;
4
+ let userName;
5
+ let appName;
6
+ let participants = {};
7
+ let currentRtcPeer;
8
+ let iceServers = [];
9
+
10
+ let socket = props.socket;
11
+
12
+ let proctoringData = document.getElementById("proctoring-data");
13
+ appName = proctoringData.dataset.appName;
14
+ roomName = props.event.toString();
15
+ userName = props.user.toString();
16
+
17
+ if (roomName && userName) {
18
+ let message = {
19
+ event: "joinRoom",
20
+ roomName,
21
+ userName,
22
+ appName,
23
+ extraInfo: {},
24
+ };
25
+
26
+ sendMessage(message);
27
+ }
28
+
29
+ function socketListener(message) {
30
+ switch (message.event) {
31
+ case "existingParticipants":
32
+ onExistingParticipants(message.userId, message.existingUsers);
33
+ break;
34
+ case "receiveVideoAnswer":
35
+ onReceiveVideoAnswer(message.senderId, message.sdpAnswer);
36
+ break;
37
+ case "candidate":
38
+ addIceCandidate(message.userId, message.candidate);
39
+ break;
40
+ case "turnServer":
41
+ setTurnServer(message.turnserver);
42
+ break;
43
+ }
44
+ }
45
+
46
+ socket.on("signaling-message", socketListener);
47
+
48
+ function sendMessage(message) {
49
+ socket.emit("signaling-message", message);
50
+ }
51
+
52
+ function setTurnServer(turnServer) {
53
+ iceServers = turnServer;
54
+ }
55
+
56
+ function stopRecordingAndRestart() {
57
+ let message = {
58
+ event: "stopRecordingAndRestart",
59
+ appName,
60
+ };
61
+ sendMessage(message);
62
+ currentRtcPeer.dispose();
63
+ socket.removeListener("signaling-message", socketListener);
64
+ videoRecordingUsingSignalingServer(props);
65
+ }
66
+
67
+ window.onbeforeunload = function () {
68
+ currentRtcPeer.dispose();
69
+ socket.disconnect();
70
+ };
71
+
72
+ function receiveVideo(userIdWs, userNameWs) {
73
+ let video = document.createElement("video");
74
+ let div = document.createElement("div");
75
+ div.className = "videoContainer";
76
+ div.id = `participant-video-${userIdWs}-${userNameWs}`;
77
+ let name = document.createElement("div");
78
+ video.id = userIdWs;
79
+ video.autoplay = true;
80
+ name.appendChild(document.createTextNode(userNameWs));
81
+ div.appendChild(video);
82
+ div.appendChild(name);
83
+ // divMeetingRoom.appendChild(div);
84
+
85
+ const onOffer = (_err, offer, _wp) => {
86
+ let message = {
87
+ event: "receiveVideoFrom",
88
+ userId: user.id,
89
+ roomName: roomName,
90
+ sdpOffer: offer,
91
+ };
92
+ sendMessage(message);
93
+ };
94
+
95
+ // send Icecandidate
96
+ const onIceCandidate = (candidate, wp) => {
97
+ var message = {
98
+ event: "candidate",
99
+ userId: user.id,
100
+ roomName: roomName,
101
+ candidate: candidate,
102
+ };
103
+ sendMessage(message);
104
+ };
105
+
106
+ let user = {
107
+ id: userIdWs,
108
+ userName: userNameWs,
109
+ video: video,
110
+ rtcPeer: null,
111
+ };
112
+
113
+ participants[user.id] = user;
114
+
115
+ let options = {
116
+ remoteVideo: video,
117
+ onicecandidate: onIceCandidate,
118
+ };
119
+
120
+ if (iceServers) {
121
+ options.configurations = {
122
+ iceServers: iceServers,
123
+ }
124
+ }
125
+
126
+ // This is for receving candidates
127
+ user.rtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(
128
+ options,
129
+ function (err) {
130
+ if (err) {
131
+ return console.error(err);
132
+ }
133
+ this.generateOffer(onOffer);
134
+ }
135
+ );
136
+ }
137
+
138
+ function onExistingParticipants(userIdWs, existingUsers) {
139
+ let video = document.createElement("video");
140
+ video.id = userIdWs;
141
+ video.autoplay = true;
142
+
143
+ let user = {
144
+ id: userIdWs,
145
+ userName: userName,
146
+ video: video,
147
+ rtcPeer: null,
148
+ };
149
+
150
+ participants[user.id] = user;
151
+
152
+ let constraints = {
153
+ audio: true,
154
+ video: {
155
+ mandatory: {
156
+ maxWidth: 320,
157
+ maxFrameRate: 15,
158
+ minFrameRate: 5,
159
+ },
160
+ },
161
+ };
162
+
163
+ const onOffer = (_err, offer, _wp) => {
164
+ console.log("On Offer");
165
+ let message = {
166
+ event: "receiveVideoFrom",
167
+ userId: user.id,
168
+ roomName: roomName,
169
+ sdpOffer: offer,
170
+ };
171
+ sendMessage(message);
172
+ };
173
+
174
+ // send Icecandidate
175
+ const onIceCandidate = (candidate, wp) => {
176
+ var message = {
177
+ event: "candidate",
178
+ userId: user.id,
179
+ roomName: roomName,
180
+ candidate: candidate,
181
+ };
182
+ sendMessage(message);
183
+ };
184
+
185
+ let options = {
186
+ localVideo: video,
187
+ mediaConstraints: constraints,
188
+ onicecandidate: onIceCandidate,
189
+ };
190
+
191
+ if (iceServers) {
192
+ options.configurations = {
193
+ iceServers: iceServers,
194
+ }
195
+ }
196
+
197
+ // This is for sending candidate
198
+ user.rtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(
199
+ options,
200
+ function (err) {
201
+ if (err) {
202
+ return console.error(err);
203
+ }
204
+ this.generateOffer(onOffer);
205
+ }
206
+ );
207
+
208
+ currentRtcPeer = user.rtcPeer;
209
+
210
+ setTimeout(() => {
211
+ stopRecordingAndRestart();
212
+ }, 5*60*1000);
213
+ }
214
+
215
+ function onReceiveVideoAnswer(senderId, sdpAnswer) {
216
+ const user = participants[senderId];
217
+ if (user) user.rtcPeer.processAnswer(sdpAnswer);
218
+ }
219
+
220
+ function addIceCandidate(userId, candidate) {
221
+ const user = participants[userId];
222
+ if (user) user.rtcPeer.addIceCandidate(candidate);
223
+ }
224
+ };
@@ -0,0 +1,299 @@
1
+ (function(exports){
2
+
3
+ /**
4
+ * toString() reference.
5
+ */
6
+ var toString = Object.prototype.toString;
7
+
8
+ /**
9
+ * slice() reference.
10
+ */
11
+ var slice = Array.prototype.slice;
12
+
13
+ /**
14
+ * setImmediate() in node.js environment
15
+ */
16
+ if (typeof window === 'undefined') {
17
+ setImmediate = global.setImmediate || process.nextTick
18
+ }
19
+
20
+ /**
21
+ * Wrap the given generator `fn` and
22
+ * return a thunk.
23
+ *
24
+ * @param {Function} fn
25
+ * @return {Function}
26
+ * @api public
27
+ */
28
+
29
+ var co = function co(fn) {
30
+ var isGenFun = isGeneratorFunction(fn);
31
+
32
+ return function (done) {
33
+ var ctx = this;
34
+
35
+ // in toThunk() below we invoke co()
36
+ // with a generator, so optimize for
37
+ // this case
38
+ var gen = fn;
39
+
40
+ // we only need to parse the arguments
41
+ // if gen is a generator function.
42
+ if (isGenFun) {
43
+ var args = slice.call(arguments), len = args.length;
44
+ var hasCallback = len && 'function' == typeof args[len - 1];
45
+ done = hasCallback ? args.pop() : error;
46
+ gen = fn.apply(this, args);
47
+ } else {
48
+ done = done || error;
49
+ }
50
+
51
+ next();
52
+
53
+ // #92
54
+ // wrap the callback in a setImmediate
55
+ // so that any of its errors aren't caught by `co`
56
+ function exit(err, res) {
57
+ setImmediate(done.bind(ctx, err, res));
58
+ }
59
+
60
+ function next(err, res) {
61
+ var ret;
62
+
63
+ // multiple args
64
+ if (arguments.length > 2) res = slice.call(arguments, 1);
65
+
66
+ // error
67
+ if (err) {
68
+ try {
69
+ ret = gen.throw(err);
70
+ } catch (e) {
71
+ return exit(e);
72
+ }
73
+ }
74
+
75
+ // ok
76
+ if (!err) {
77
+ try {
78
+ ret = gen.next(res);
79
+ } catch (e) {
80
+ return exit(e);
81
+ }
82
+ }
83
+
84
+ // done
85
+ if (ret.done) return exit(null, ret.value);
86
+
87
+ // normalize
88
+ ret.value = toThunk(ret.value, ctx);
89
+
90
+ // run
91
+ if ('function' == typeof ret.value) {
92
+ var called = false;
93
+ try {
94
+ ret.value.call(ctx, function(){
95
+ if (called) return;
96
+ called = true;
97
+ next.apply(ctx, arguments);
98
+ });
99
+ } catch (e) {
100
+ setImmediate(function(){
101
+ if (called) return;
102
+ called = true;
103
+ next(e);
104
+ });
105
+ }
106
+ return;
107
+ }
108
+
109
+ // invalid
110
+ next(new Error('yield a function, promise, generator, array, or object'));
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Convert `obj` into a normalized thunk.
117
+ *
118
+ * @param {Mixed} obj
119
+ * @param {Mixed} ctx
120
+ * @return {Function}
121
+ * @api private
122
+ */
123
+
124
+ function toThunk(obj, ctx) {
125
+
126
+ if (isGeneratorFunction(obj)) {
127
+ return co(obj.call(ctx));
128
+ }
129
+
130
+ if (isGenerator(obj)) {
131
+ return co(obj);
132
+ }
133
+
134
+ if (isPromise(obj)) {
135
+ return promiseToThunk(obj);
136
+ }
137
+
138
+ if ('function' == typeof obj) {
139
+ return obj;
140
+ }
141
+
142
+ if (isObject(obj) || Array.isArray(obj)) {
143
+ return objectToThunk.call(ctx, obj);
144
+ }
145
+
146
+ return obj;
147
+ }
148
+
149
+ /**
150
+ * Convert an object of yieldables to a thunk.
151
+ *
152
+ * @param {Object} obj
153
+ * @return {Function}
154
+ * @api private
155
+ */
156
+
157
+ function objectToThunk(obj){
158
+ var ctx = this;
159
+
160
+ return function(done){
161
+ var keys = Object.keys(obj);
162
+ var pending = keys.length;
163
+ var results = new obj.constructor();
164
+ var finished;
165
+
166
+ if (!pending) {
167
+ setImmediate(function(){
168
+ done(null, results)
169
+ });
170
+ return;
171
+ }
172
+
173
+ for (var i = 0; i < keys.length; i++) {
174
+ run(obj[keys[i]], keys[i]);
175
+ }
176
+
177
+ function run(fn, key) {
178
+ if (finished) return;
179
+ try {
180
+ fn = toThunk(fn, ctx);
181
+
182
+ if ('function' != typeof fn) {
183
+ results[key] = fn;
184
+ return --pending || done(null, results);
185
+ }
186
+
187
+ fn.call(ctx, function(err, res){
188
+ if (finished) return;
189
+
190
+ if (err) {
191
+ finished = true;
192
+ return done(err);
193
+ }
194
+
195
+ results[key] = res;
196
+ --pending || done(null, results);
197
+ });
198
+ } catch (err) {
199
+ finished = true;
200
+ done(err);
201
+ }
202
+ }
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Convert `promise` to a thunk.
208
+ *
209
+ * @param {Object} promise
210
+ * @return {Function}
211
+ * @api private
212
+ */
213
+
214
+ function promiseToThunk(promise) {
215
+ return function(fn){
216
+ promise.then(function(res) {
217
+ fn(null, res);
218
+ }, fn);
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Check if `obj` is a promise.
224
+ *
225
+ * @param {Object} obj
226
+ * @return {Boolean}
227
+ * @api private
228
+ */
229
+
230
+ function isPromise(obj) {
231
+ return obj && 'function' == typeof obj.then;
232
+ }
233
+
234
+ /**
235
+ * Check if `obj` is a generator.
236
+ *
237
+ * @param {Mixed} obj
238
+ * @return {Boolean}
239
+ * @api private
240
+ */
241
+
242
+ function isGenerator(obj) {
243
+ return obj && 'function' == typeof obj.next && 'function' == typeof obj.throw;
244
+ }
245
+
246
+ /**
247
+ * Check if `obj` is a generator function.
248
+ *
249
+ * @param {Mixed} obj
250
+ * @return {Boolean}
251
+ * @api private
252
+ */
253
+
254
+ function isGeneratorFunction(obj) {
255
+ return obj && obj.constructor && 'GeneratorFunction' == obj.constructor.name;
256
+ }
257
+
258
+ /**
259
+ * Check for plain object.
260
+ *
261
+ * @param {Mixed} val
262
+ * @return {Boolean}
263
+ * @api private
264
+ */
265
+
266
+ function isObject(val) {
267
+ return val && Object == val.constructor;
268
+ }
269
+
270
+ /**
271
+ * Throw `err` in a new stack.
272
+ *
273
+ * This is used when co() is invoked
274
+ * without supplying a callback, which
275
+ * should only be for demonstrational
276
+ * purposes.
277
+ *
278
+ * @param {Error} err
279
+ * @api private
280
+ */
281
+
282
+ function error(err) {
283
+ if (!err) return;
284
+ setImmediate(function(){
285
+ throw err;
286
+ });
287
+ }
288
+
289
+ /**
290
+ * Set module reference
291
+ */
292
+ if(typeof window === 'undefined') {
293
+ module.exports = co;
294
+ } else {
295
+ exports['co'] = co;
296
+ }
297
+
298
+ })(typeof module === 'undefined' ? this : module);
299
+