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,344 @@
1
+ function liveVideoUsingSignalingServer(props) {
2
+ // variables
3
+ let roomName;
4
+ let userName;
5
+ let appName;
6
+ let participants = {};
7
+ let currentRtcPeer;
8
+ const iceCandidateQueue = {};
9
+ const connectedEvent = document.getElementById("connected-event");
10
+ const assignedUsers = document.getElementById("assigned-candidates");
11
+ const connectedUsers = document.getElementById("connected-candidates");
12
+ const connectedAdminUsers = document.getElementById("connected-recruiters");
13
+ const connectedUsersList = document.getElementById("connected-candidates-list");
14
+ const updateTimer = 5 * 1000; // 5 seconds
15
+ const { socket, event, user, assignedUserIds } = props;
16
+
17
+ var divMeetingRoom = document.getElementById(
18
+ props.videoDivId || "proctoringVideos"
19
+ );
20
+
21
+ let proctoringData = document.getElementById("proctoring-data");
22
+ appName = proctoringData.dataset.appName;
23
+ roomName = props.event.toString();
24
+ userName = props.user.toString();
25
+ adminUserName = userName + '-admin'
26
+ if (roomName && userName) {
27
+ let message = {
28
+ event: "joinRoom",
29
+ roomName,
30
+ userName: adminUserName,
31
+ appName,
32
+ extraInfo: {},
33
+ };
34
+
35
+ sendMessage(message);
36
+ }
37
+
38
+ function socketListener(message) {
39
+ console.log("Message arrived", message);
40
+
41
+ switch (message.event) {
42
+ case "newParticipantArrived":
43
+ onNewParticipant(message.userId, message.userName, message.roomName);
44
+ break;
45
+ case "existingParticipants":
46
+ onExistingParticipants(message.userId, message.existingUsers);
47
+ break;
48
+ case "receiveVideoAnswer":
49
+ onReceiveVideoAnswer(message.senderId, message.sdpAnswer);
50
+ break;
51
+ case "participantLeft":
52
+ setOffline(message.userName);
53
+ break;
54
+ case "candidate":
55
+ addIceCandidate(message.userId, message.candidate);
56
+ break;
57
+ case "analytics-data":
58
+ setUpAnalytics(message.roomInfo);
59
+ break;
60
+ }
61
+ }
62
+
63
+ socket.on("signaling-message", socketListener);
64
+ getLiveVideoProctoringAnalyticsData({ socket, event });
65
+ setInterval(() => {
66
+ getLiveVideoProctoringAnalyticsData({ socket, event });
67
+ }, updateTimer);
68
+
69
+ function sendMessage(message) {
70
+ console.log("sending " + message.event + " message to server");
71
+ socket.emit("signaling-message", message);
72
+ }
73
+
74
+ function getLiveVideoProctoringAnalyticsData(props) {
75
+ let roomName = props.event.toString();
76
+ let message = {
77
+ event: "analytics-data",
78
+ roomName,
79
+ };
80
+ sendMessage(message);
81
+ }
82
+
83
+
84
+ function stopRecordingAndRestart() {
85
+ let message = {
86
+ event: "stopRecordingAndRestart",
87
+ appName,
88
+ };
89
+ sendMessage(message);
90
+ currentRtcPeer.dispose();
91
+ socket.removeListener("signaling-message", socketListener);
92
+ liveVideoUsingSignalingServer(props);
93
+ }
94
+
95
+ window.onbeforeunload = function () {
96
+ currentRtcPeer.dispose();
97
+ socket.disconnect();
98
+ };
99
+
100
+ function setOffline(userid) {
101
+ const container = document.getElementById(`participant-video-${userid}`);
102
+ if(container) {
103
+ container.classList.remove("border-success");
104
+ container.classList.add("border-danger");
105
+ // const callButton = container.querySelector(".connect-candidate");
106
+ // callButton.disabled = true;
107
+ }
108
+ }
109
+
110
+ function receiveVideo(userIdWs, userNameWs) {
111
+ const checkContainer = document.getElementById(
112
+ `participant-video-${userNameWs}`
113
+ );
114
+ let video, div;
115
+ if (checkContainer) {
116
+ div = checkContainer;
117
+ const videoElm = checkContainer.querySelector('video');
118
+ video = videoElm;
119
+ } else {
120
+ const nodeToCopy = document.getElementById("sample-video-div").querySelector('div');
121
+ const newDiv = nodeToCopy.cloneNode(true);
122
+ div = newDiv;
123
+ video = newDiv.querySelector('video');
124
+ let name = newDiv.querySelector(".video-user-id");
125
+ name.innerText = userNameWs;
126
+ div.id = `participant-video-${userNameWs}`;
127
+ video.id = `video-elm-${userNameWs}`;
128
+ video.muted = true;
129
+ divMeetingRoom.appendChild(div);
130
+ }
131
+
132
+ if(div) {
133
+ div.classList.remove("border-danger");
134
+ div.classList.add("border-success");
135
+ // const callButton = div.querySelector(".connect-candidate");
136
+ // callButton.disabled = false;
137
+ }
138
+
139
+ const onOffer = (_err, offer, _wp) => {
140
+ // console.log("On Offer");
141
+ let message = {
142
+ event: "receiveVideoFrom",
143
+ userId: user.id,
144
+ roomName: roomName,
145
+ sdpOffer: offer,
146
+ };
147
+ sendMessage(message);
148
+ };
149
+
150
+ // send Icecandidate
151
+ const onIceCandidate = (candidate, wp) => {
152
+ // console.log("sending ice candidates");
153
+ var message = {
154
+ event: "candidate",
155
+ userId: user.id,
156
+ roomName: roomName,
157
+ candidate: candidate,
158
+ };
159
+ sendMessage(message);
160
+ };
161
+
162
+ let user = {
163
+ id: userIdWs,
164
+ userName: userNameWs,
165
+ video: video,
166
+ rtcPeer: null,
167
+ };
168
+
169
+ participants[user.id] = user;
170
+
171
+ let options = {
172
+ remoteVideo: video,
173
+ onicecandidate: onIceCandidate,
174
+ };
175
+
176
+ // This is for receving candidates
177
+ user.rtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(
178
+ options,
179
+ function (err) {
180
+ if (err) {
181
+ return console.error(err);
182
+ }
183
+ if (iceCandidateQueue) {
184
+ while (iceCandidateQueue.length) {
185
+ const ice = iceCandidateQueue.shift();
186
+ user.rtcPeer.addIceCandidate(ice.candidate);
187
+ }
188
+ }
189
+ this.generateOffer(onOffer);
190
+ }
191
+ );
192
+ }
193
+
194
+ function onNewParticipant(userIdWs, userNameWs, roomNameWs) {
195
+ if (validCandidate(userNameWs, roomNameWs)) receiveVideo(userIdWs, userNameWs);
196
+ }
197
+
198
+ function onExistingParticipants(userIdWs, existingUsers) {
199
+ let video = document.createElement("video");
200
+ video.id = userIdWs;
201
+ video.autoplay = false;
202
+
203
+ let user = {
204
+ id: userIdWs,
205
+ userName: userName,
206
+ video: video,
207
+ rtcPeer: null,
208
+ };
209
+
210
+ participants[user.id] = user;
211
+
212
+ let constraints = {
213
+ audio: true,
214
+ video: {
215
+ width: { min: 320, ideal: 320, max: 640 },
216
+ height: { min: 240, ideal: 240, max: 480 },
217
+ },
218
+ };
219
+
220
+ const onOffer = (_err, offer, _wp) => {
221
+ // console.log("On Offer");
222
+ let message = {
223
+ event: "receiveVideoFrom",
224
+ userId: user.id,
225
+ roomName: roomName,
226
+ sdpOffer: offer,
227
+ };
228
+ sendMessage(message);
229
+ };
230
+
231
+ // send Icecandidate
232
+ const onIceCandidate = (candidate, wp) => {
233
+ // console.log("sending ice candidates");
234
+ var message = {
235
+ event: "candidate",
236
+ userId: user.id,
237
+ roomName: roomName,
238
+ candidate: candidate,
239
+ };
240
+ sendMessage(message);
241
+ };
242
+
243
+ let options = {
244
+ // localVideo: video,
245
+ // mediaConstraints: constraints,
246
+ onicecandidate: onIceCandidate,
247
+ };
248
+
249
+ // This is for sending candidate
250
+ user.rtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(
251
+ options,
252
+ function (err) {
253
+ if (err) {
254
+ return console.error(err);
255
+ }
256
+ if (iceCandidateQueue) {
257
+ while (iceCandidateQueue.length) {
258
+ const ice = iceCandidateQueue.shift();
259
+ user.rtcPeer.addIceCandidate(ice.candidate);
260
+ }
261
+ }
262
+ this.generateOffer(onOffer);
263
+ }
264
+ );
265
+
266
+ existingUsers.forEach(function (element) {
267
+ if (validCandidate(element.name, element.roomName)) receiveVideo(element.id, element.name);
268
+ });
269
+
270
+ currentRtcPeer = user.rtcPeer;
271
+
272
+ setTimeout(() => {
273
+ stopRecordingAndRestart();
274
+ }, 5*60*1000);
275
+ }
276
+
277
+ function setUpAnalytics(roomInfo) {
278
+ connectedEvent.innerText = event;
279
+ let candidateCount = 0;
280
+ let adminCount = 0;
281
+ let assignedCount = 0;
282
+
283
+ if (roomInfo) {
284
+ Object.keys(roomInfo).forEach((key) => {
285
+ if (checkAdminUser(key)) adminCount += 1;
286
+ else candidateCount += 1;
287
+
288
+ if (validCandidate(key, roomInfo[key].room)) assignedCount += 1;
289
+ });
290
+ }
291
+ assignedUsers.innerText = `${assignedCount}/${JSON.parse(assignedUserIds).length}`;
292
+ connectedUsers.innerText = candidateCount;
293
+ connectedAdminUsers.innerText = adminCount;
294
+ // List update
295
+ let div = document.createElement("div");
296
+ div.className = "list-group";
297
+ listClass = "list-group-item list-group-item-action rounded-0";
298
+ if (roomInfo) {
299
+ Object.keys(roomInfo).forEach((key) => {
300
+ if (validCandidate(key, roomInfo[key].room)) {
301
+ let link = document.createElement('a');
302
+ const text = document.createTextNode(key);
303
+ link.appendChild(text);
304
+ link.id = `candidate-list-elm-${key}`;
305
+ link.className = listClass;
306
+ div.appendChild(link);
307
+ }
308
+ })
309
+ } else {
310
+ let link = document.createElement("a");
311
+ const text = document.createTextNode("No users connected!");
312
+ link.appendChild(text);
313
+ link.className = listClass;
314
+ div.appendChild(link);
315
+ }
316
+ connectedUsersList.innerHTML = "";
317
+ connectedUsersList.appendChild(div);
318
+ }
319
+
320
+ function validCandidate(candidateUserId, room) {
321
+ if (checkAdminUser(candidateUserId)) return false;
322
+ if (roomName !== room) return false;
323
+ if (!assignedUserIds.includes(parseInt(candidateUserId))) return false;
324
+
325
+ return true;
326
+ }
327
+
328
+ function checkAdminUser(userName) {
329
+ return userName.split('-').includes('admin');
330
+ }
331
+
332
+ function onReceiveVideoAnswer(senderId, sdpAnswer) {
333
+ participants[senderId].rtcPeer.processAnswer(sdpAnswer);
334
+ }
335
+
336
+ function addIceCandidate(userId, candidate) {
337
+ const user = participants[userId]
338
+ if (user) participants[userId].rtcPeer.addIceCandidate(candidate);
339
+ else {
340
+ if (!iceCandidateQueue[userId]) iceCandidateQueue[userId] = [];
341
+ iceCandidateQueue[userId].push({ candidate });
342
+ }
343
+ }
344
+ };
@@ -0,0 +1,63 @@
1
+ class VideoPlayer {
2
+ constructor(elementId='preview-player', playlist) {
3
+ this.player = videojs(elementId, {
4
+ fluid: true,
5
+ });
6
+ const defaultDataEl = document.getElementById('video-player-proctoring');
7
+ if(defaultDataEl && defaultDataEl.dataset.defaultThumbnail) {
8
+ this.thumbnail = defaultDataEl.dataset.defaultThumbnail;
9
+ }
10
+ }
11
+
12
+ playPlaylist(playlist) {
13
+ if (this.thumbnail) {
14
+ this.playlist = playlist.map((list) => {
15
+ if (!list.thumbnail) {
16
+ list.thumbnail = [{ src: this.thumbnail }];
17
+ }
18
+ return list;
19
+ });
20
+ } else {
21
+ this.playlist = playlist;
22
+ }
23
+ this.player.playlist(this.playlist);
24
+ // playlist structure
25
+ // [{
26
+ // name: "Sintel open movie",
27
+ // description: "Explore the depths of our planet's oceans. ",
28
+ // duration: 10,
29
+ // // src: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
30
+ // sources: [
31
+ // {
32
+ // src: "http://media.w3.org/2010/05/sintel/trailer.mp4",
33
+ // type: "video/mp4",
34
+ // },
35
+ // ],
36
+ // thumbnail: [{ src: "http://media.w3.org/2010/05/sintel/poster.png" }],
37
+ // },
38
+ // {
39
+ // name: "Sintel open movie",
40
+ // description: "Explore the depths of our planet's oceans. ",
41
+ // duration: 10,
42
+ // // src: 'http://media.w3.org/2010/05/sintel/trailer.mp4',
43
+ // sources: [
44
+ // {
45
+ // src: "http://media.w3.org/2010/05/sintel/trailer.mp4",
46
+ // type: "video/mp4",
47
+ // },
48
+ // ],
49
+ // thumbnail: [{ src: "http://media.w3.org/2010/05/sintel/poster.png" }],
50
+ // }];
51
+ // populate playlist UI
52
+ this.player.playlistUi();
53
+ // Auto advance one video after another
54
+ this.player.playlist.autoadvance(0);
55
+ }
56
+
57
+ pauseVideo() {
58
+ if (this.player) {
59
+ this.player.pause();
60
+ }
61
+ }
62
+
63
+ }
@@ -0,0 +1,286 @@
1
+ class VideoRecording {
2
+ constructor({
3
+ event,
4
+ user,
5
+ inputVideoElmId = false,
6
+ proctoringDataElmId = "proctoring-data",
7
+ iceServers = undefined,
8
+ }) {
9
+ this.webRtcPeer;
10
+ this.client;
11
+ this.pipeline;
12
+ this.recorder;
13
+ this.mediaServerUrl;
14
+ this.player;
15
+ this.eventName = event;
16
+ this.user = user;
17
+ this.retryCount = 0;
18
+ if (this.inputVideoElmId) {
19
+ this.inputVideoElm = document.getElementById(inputVideoElmId);
20
+ this.showInputVideo = true;
21
+ }
22
+ const proctoringData = document.getElementById(proctoringDataElmId);
23
+ if (proctoringData) {
24
+ this.mediaServerUrl = proctoringData.dataset.mediaServerUrl;
25
+ this.appName = proctoringData.dataset.appName;
26
+ try {
27
+ this.iceServers = JSON.parse(proctoringData.dataset.iceServers);
28
+ } catch(error) {
29
+ console.log("Ice servers not defined!");
30
+ }
31
+ } else {
32
+ this.mediaServerUrl = window.location.host;
33
+ this.appName = window.location.host;
34
+ this.iceServers = undefined;
35
+ }
36
+ this.streamConfig = {
37
+ ws_uri: `ws${location.protocol === "http:" ? "" : "s"}://${
38
+ this.mediaServerUrl
39
+ }/kurento`,
40
+ ice_servers: this.iceServers,
41
+ };
42
+ this.onStartOffer = this.onStartOffer.bind(this);
43
+ this.onPlayOffer = this.onPlayOffer.bind(this);
44
+ }
45
+
46
+ setIceCandidateCallbacks(webRtcPeer, webRtcEp, onerror) {
47
+ webRtcPeer.on("icecandidate", function (candidate) {
48
+ // console.log("Local candidate:",candidate);
49
+
50
+ candidate = kurentoClient.getComplexType("IceCandidate")(candidate);
51
+
52
+ webRtcEp.addIceCandidate(candidate, onerror);
53
+ });
54
+
55
+ webRtcEp.on("OnIceCandidate", function (event) {
56
+ let candidate = event.candidate;
57
+
58
+ // console.log("Remote candidate:",candidate);
59
+
60
+ webRtcPeer.addIceCandidate(candidate, onerror);
61
+ });
62
+ webRtcEp.on("ConnectionStateChanged", (event) => {
63
+ if (event.newState === "DISCONNECTED") {
64
+ this.connectionStatus = null;
65
+ } else {
66
+ this.connectionStatus = event.newState;
67
+ }
68
+ this.clearTimeDilation();
69
+ });
70
+ }
71
+
72
+ startRecordingSingleSession() {
73
+ const { eventName, user, appName } = this;
74
+ this.streamConfig.file_uri = `file:///recordings/app_data/${appName}/video/${eventName}/${user}/${user}-${+new Date()}.webm`;
75
+ let constraints = {
76
+ audio: true,
77
+ video: {
78
+ width: 640,
79
+ framerate: 15,
80
+ },
81
+ };
82
+ let options = {
83
+ mediaConstraints: constraints,
84
+ };
85
+ if (this.showInputVideo) {
86
+ options.localVideo = this.inputVideoElm;
87
+ this.inputVideoElm.muted = true;
88
+ }
89
+
90
+ if (this.streamConfig.ice_servers) {
91
+ // console.log("Use ICE servers: ", this.streamConfig.ice_servers);
92
+ options.configuration = {
93
+ iceServers: this.streamConfig.ice_servers,
94
+ };
95
+ } else {
96
+ console.log("Use freeice");
97
+ }
98
+ const self = this;
99
+ self.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(
100
+ options,
101
+ function (error) {
102
+ if (error) return self.onError(error);
103
+
104
+ this.generateOffer(self.onStartOffer);
105
+ }
106
+ );
107
+ }
108
+
109
+ onStartOffer(error, sdpOffer) {
110
+ const self = this;
111
+ if (error) return self.onError(error);
112
+
113
+ co(function* () {
114
+ try {
115
+ if (!self.client)
116
+ self.client = yield kurentoClient(self.streamConfig.ws_uri);
117
+
118
+ self.pipeline = yield self.client.create("MediaPipeline");
119
+
120
+ let webRtc = yield self.pipeline.create("WebRtcEndpoint");
121
+ self.setIceCandidateCallbacks(self.webRtcPeer, webRtc, self.onError);
122
+
123
+ self.recorder = yield self.pipeline.create("RecorderEndpoint", {
124
+ uri: self.streamConfig.file_uri,
125
+ });
126
+
127
+ yield webRtc.connect(self.recorder);
128
+ yield webRtc.connect(self.webRtc);
129
+
130
+ yield self.recorder.record();
131
+
132
+ let sdpAnswer = yield webRtc.processOffer(sdpOffer);
133
+ webRtc.gatherCandidates(self.onError);
134
+ self.webRtcPeer.processAnswer(sdpAnswer);
135
+
136
+ // setStatus(CALLING);
137
+ } catch (e) {
138
+ self.onError(e);
139
+ }
140
+ })();
141
+ }
142
+
143
+ playVideo(elm = "videoOutput") {
144
+ const videoOutput = document.getElementById(elm);
145
+ let options = {
146
+ remoteVideo: videoOutput,
147
+ };
148
+
149
+ if (this.streamConfig.ice_servers) {
150
+ console.log("Use ICE servers: " + this.streamConfig.ice_servers);
151
+ options.configuration = {
152
+ iceServers: this.streamConfig.ice_servers,
153
+ };
154
+ } else {
155
+ console.log("Use freeice");
156
+ }
157
+ const self = this;
158
+ self.webRtcPeer = kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(
159
+ options,
160
+ function (error) {
161
+ if (error) return onError(error);
162
+
163
+ this.generateOffer(self.onPlayOffer);
164
+ }
165
+ );
166
+ }
167
+
168
+ stopAndPlayVideo(elm = "videoOutput") {
169
+ this.stopRecording();
170
+ this.playVideo(elm);
171
+ }
172
+
173
+ onPlayOffer(error, sdpOffer) {
174
+ const self = this;
175
+ if (error) return self.onError(error);
176
+
177
+ co(function* () {
178
+ try {
179
+ if (!self.client)
180
+ self.client = yield kurentoClient(self.streamConfig.ws_uri);
181
+
182
+ self.pipeline = yield self.client.create("MediaPipeline");
183
+
184
+ let webRtc = yield self.pipeline.create("WebRtcEndpoint");
185
+ self.setIceCandidateCallbacks(self.webRtcPeer, webRtc, self.onError);
186
+
187
+ self.player = yield self.pipeline.create("PlayerEndpoint", {
188
+ uri: self.streamConfig.file_uri,
189
+ });
190
+
191
+ self.player.on("EndOfStream", self.stopRecording);
192
+
193
+ yield self.player.connect(webRtc);
194
+
195
+ let sdpAnswer = yield webRtc.processOffer(sdpOffer);
196
+ webRtc.gatherCandidates(self.onError);
197
+ self.webRtcPeer.processAnswer(sdpAnswer);
198
+
199
+ yield self.player.play();
200
+ } catch (e) {
201
+ self.onError(e);
202
+ }
203
+ })();
204
+ }
205
+
206
+ onError(error) {
207
+ if (error) {
208
+ console.error(error);
209
+ this.stopRecording();
210
+ this.retryCount += 1;
211
+ if (this.retryCount < 5) {
212
+ setTimeout(() => {
213
+ if (this.interval) {
214
+ this.startRecordingSingleSessionWithInterval(this.interval);
215
+ }
216
+ }, 1000 * this.retryCount);
217
+ } else {
218
+ throw "Something went wrong with video recording.";
219
+ }
220
+ }
221
+ }
222
+
223
+ stopRecording() {
224
+ if (this.recorder) {
225
+ this.recorder.stop();
226
+ this.recorder = null;
227
+ }
228
+ if (this.webRtcPeer) {
229
+ this.webRtcPeer.dispose();
230
+ this.webRtcPeer = null;
231
+ }
232
+ if (this.pipeline) {
233
+ this.pipeline.release();
234
+ this.pipeline = null;
235
+ }
236
+ }
237
+
238
+ startRecordingSingleSessionWithInterval(interval = 30000) {
239
+ if (typeof interval !== "number" || interval < 30000) {
240
+ // In ms
241
+ throw "Interval for single session must be more than or equal to 30sec.";
242
+ }
243
+ this.interval = interval;
244
+ this.startRecordingSingleSession();
245
+ const self = this;
246
+ setInterval(() => {
247
+ console.log("retry");
248
+ if (self.recorder || self.pipeline || self.webRtcPeer) {
249
+ self.stopRecording();
250
+ }
251
+ setTimeout(() => {
252
+ self.startRecordingSingleSession();
253
+ }, 300);
254
+ }, interval + 300);
255
+ }
256
+
257
+ recordAndPlaySessionWithTimeout(timeout = 10000, videInput, videoOutput) {
258
+ if (typeof timeout !== "number" || timeout < 10000) {
259
+ throw "Timeout for single session must be more than or equal to 10sec.";
260
+ }
261
+ if (videInput) {
262
+ this.inputVideoElm = document.getElementById(videInput);
263
+ this.showInputVideo = true;
264
+ }
265
+ this.startRecordingSingleSession();
266
+ this.setupTimeDilation();
267
+ setTimeout(() => {
268
+ this.stopAndPlayVideoAfterTimeAddition(videoOutput);
269
+ }, timeout); // extra buffer of 300ms
270
+ }
271
+
272
+ stopAndPlayVideoAfterTimeAddition(videoOutput) {
273
+ setTimeout(() => this.stopAndPlayVideo(videoOutput), this.timeDilation);
274
+ }
275
+
276
+ setupTimeDilation() {
277
+ this.timeDilation = 0;
278
+ this.timeDilationInterval = setInterval(() => {
279
+ this.timeDilation += 1000;
280
+ }, 1000);
281
+ }
282
+
283
+ clearTimeDilation() {
284
+ if (this.timeDilationInterval) clearInterval(this.timeDilationInterval);
285
+ }
286
+ }