Merge branch 'MDL-68436' of https://github.com/timhunt/moodle
[moodle.git] / lib / editor / atto / plugins / recordrtc / yui / build / moodle-atto_recordrtc-recording / moodle-atto_recordrtc-recording.js
CommitLineData
b4bbf31b 1YUI.add('moodle-atto_recordrtc-recording', function (Y, NAME) {
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17//
18
19/**
20 * Atto recordrtc library functions
21 *
22 * @package atto_recordrtc
23 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
24 * @author Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
25 * @copyright 2017 Blindside Networks Inc.
26 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 */
28
29// ESLint directives.
30/* eslint-disable camelcase, no-alert, spaced-comment */
31
32// JSHint directives.
33/*global M */
34/*jshint es5: true */
35/*jshint onevar: false */
36/*jshint shadow: true */
37
38// Scrutinizer CI directives.
39/** global: M */
40/** global: Y */
41
42M.atto_recordrtc = M.atto_recordrtc || {};
43
44// Shorten access to M.atto_recordrtc.commonmodule namespace.
45var cm = M.atto_recordrtc.commonmodule,
46 am = M.atto_recordrtc.abstractmodule;
47
48M.atto_recordrtc.commonmodule = {
49 // Unitialized variables to be used by the other modules.
50 editorScope: null,
51 alertWarning: null,
52 alertDanger: null,
53 player: null,
54 playerDOM: null, // Used to manipulate DOM directly.
55 startStopBtn: null,
56 uploadBtn: null,
57 countdownSeconds: null,
58 countdownTicker: null,
59 recType: null,
60 stream: null,
61 mediaRecorder: null,
62 chunks: null,
63 blobSize: null,
b4bbf31b 64 maxUploadSize: null,
65
66 // Capture webcam/microphone stream.
67 capture_user_media: function(mediaConstraints, successCallback, errorCallback) {
68 window.navigator.mediaDevices.getUserMedia(mediaConstraints).then(successCallback).catch(errorCallback);
69 },
70
71 // Add chunks of audio/video to array when made available.
72 handle_data_available: function(event) {
73 // Push recording slice to array.
74 cm.chunks.push(event.data);
75 // Size of all recorded data so far.
76 cm.blobSize += event.data.size;
77
78 // If total size of recording so far exceeds max upload limit, stop recording.
79 // An extra condition exists to avoid displaying alert twice.
80 if (cm.blobSize >= cm.maxUploadSize) {
81 if (!window.localStorage.getItem('alerted')) {
82 window.localStorage.setItem('alerted', 'true');
83
84 cm.startStopBtn.simulate('click');
85 am.show_alert('nearingmaxsize');
86 } else {
87 window.localStorage.removeItem('alerted');
88 }
89
90 cm.chunks.pop();
91 }
92 },
93
94 // Handle recording end.
95 handle_stop: function() {
96 // Set source of audio player.
97 var blob = new window.Blob(cm.chunks, {type: cm.mediaRecorder.mimeType});
07cc7007 98 cm.player.set('srcObject', null);
b4bbf31b 99 cm.player.set('src', window.URL.createObjectURL(blob));
100
101 // Show audio player with controls enabled, and unmute.
102 cm.player.set('muted', false);
103 cm.player.set('controls', true);
104 cm.player.ancestor().ancestor().removeClass('hide');
105
106 // Show upload button.
107 cm.uploadBtn.ancestor().ancestor().removeClass('hide');
108 cm.uploadBtn.set('textContent', M.util.get_string('attachrecording', 'atto_recordrtc'));
109 cm.uploadBtn.set('disabled', false);
110
111 // Get dialogue centered.
112 cm.editorScope.getDialogue().centered();
113
114 // Handle when upload button is clicked.
115 cm.uploadBtn.on('click', function() {
116 // Trigger error if no recording has been made.
117 if (cm.chunks.length === 0) {
118 am.show_alert('norecordingfound');
119 } else {
120 cm.uploadBtn.set('disabled', true);
121
122 // Upload recording to server.
123 cm.upload_to_server(cm.recType, function(progress, fileURLOrError) {
124 if (progress === 'ended') { // Insert annotation in text.
125 cm.uploadBtn.set('disabled', false);
126 cm.insert_annotation(cm.recType, fileURLOrError);
127 } else if (progress === 'upload-failed') { // Show error message in upload button.
128 cm.uploadBtn.set('disabled', false);
129 cm.uploadBtn.set('textContent',
130 M.util.get_string('uploadfailed', 'atto_recordrtc') + ' ' + fileURLOrError);
131 } else if (progress === 'upload-failed-404') { // 404 error = File too large in Moodle.
132 cm.uploadBtn.set('disabled', false);
133 cm.uploadBtn.set('textContent', M.util.get_string('uploadfailed404', 'atto_recordrtc'));
134 } else if (progress === 'upload-aborted') {
135 cm.uploadBtn.set('disabled', false);
136 cm.uploadBtn.set('textContent',
137 M.util.get_string('uploadaborted', 'atto_recordrtc') + ' ' + fileURLOrError);
138 } else {
139 cm.uploadBtn.set('textContent', progress);
140 }
141 });
142 }
143 });
144 },
145
146 // Get everything set up to start recording.
147 start_recording: function(type, stream) {
148 // The options for the recording codecs and bitrates.
149 var options = am.select_rec_options(type);
150 cm.mediaRecorder = new window.MediaRecorder(stream, options);
151
152 // Initialize MediaRecorder events and start recording.
153 cm.mediaRecorder.ondataavailable = cm.handle_data_available;
154 cm.mediaRecorder.onstop = cm.handle_stop;
155 cm.mediaRecorder.start(1000); // Capture in 1s chunks. Must be set to work with Firefox.
156
157 // Mute audio, distracting while recording.
158 cm.player.set('muted', true);
159
160 // Set recording timer to the time specified in the settings.
161 cm.countdownSeconds = cm.editorScope.get('timelimit');
162 cm.countdownSeconds++;
163 var timerText = M.util.get_string('stoprecording', 'atto_recordrtc');
164 timerText += ' (<span id="minutes"></span>:<span id="seconds"></span>)';
165 cm.startStopBtn.setHTML(timerText);
166 cm.set_time();
167 cm.countdownTicker = window.setInterval(cm.set_time, 1000);
168
169 // Make button clickable again, to allow stopping recording.
170 cm.startStopBtn.set('disabled', false);
171 },
172
173 // Get everything set up to stop recording.
174 stop_recording: function(stream) {
175 // Stop recording stream.
176 cm.mediaRecorder.stop();
177
178 // Stop each individual MediaTrack.
179 var tracks = stream.getTracks();
180 for (var i = 0; i < tracks.length; i++) {
181 tracks[i].stop();
182 }
183 },
184
185 // Upload recorded audio/video to server.
186 upload_to_server: function(type, callback) {
187 var xhr = new window.XMLHttpRequest();
188
189 // Get src media of audio/video tag.
190 xhr.open('GET', cm.player.get('src'), true);
191 xhr.responseType = 'blob';
192
193 xhr.onload = function() {
194 if (xhr.status === 200) { // If src media was successfully retrieved.
195 // blob is now the media that the audio/video tag's src pointed to.
196 var blob = this.response;
197
198 // Generate filename with random ID and file extension.
199 var fileName = (Math.random() * 1000).toString().replace('.', '');
200 fileName += (type === 'audio') ? '-audio.ogg'
201 : '-video.webm';
202
203 // Create FormData to send to PHP filepicker-upload script.
204 var formData = new window.FormData(),
205 filepickerOptions = cm.editorScope.get('host').get('filepickeroptions').link,
206 repositoryKeys = window.Object.keys(filepickerOptions.repositories);
207
208 formData.append('repo_upload_file', blob, fileName);
209 formData.append('itemid', filepickerOptions.itemid);
210
211 for (var i = 0; i < repositoryKeys.length; i++) {
212 if (filepickerOptions.repositories[repositoryKeys[i]].type === 'upload') {
213 formData.append('repo_id', filepickerOptions.repositories[repositoryKeys[i]].id);
214 break;
215 }
216 }
217
218 formData.append('env', filepickerOptions.env);
219 formData.append('sesskey', M.cfg.sesskey);
220 formData.append('client_id', filepickerOptions.client_id);
221 formData.append('savepath', '/');
222 formData.append('ctx_id', filepickerOptions.context.id);
223
224 // Pass FormData to PHP script using XHR.
225 var uploadEndpoint = M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload';
226 cm.make_xmlhttprequest(uploadEndpoint, formData,
227 function(progress, responseText) {
228 if (progress === 'upload-ended') {
229 callback('ended', window.JSON.parse(responseText).url);
230 } else {
231 callback(progress);
232 }
233 }
234 );
235 }
236 };
237
238 xhr.send();
239 },
240
241 // Handle XHR sending/receiving/status.
242 make_xmlhttprequest: function(url, data, callback) {
243 var xhr = new window.XMLHttpRequest();
244
245 xhr.onreadystatechange = function() {
246 if ((xhr.readyState === 4) && (xhr.status === 200)) { // When request is finished and successful.
247 callback('upload-ended', xhr.responseText);
248 } else if (xhr.status === 404) { // When request returns 404 Not Found.
249 callback('upload-failed-404');
250 }
251 };
252
253 xhr.upload.onprogress = function(event) {
254 callback(Math.round(event.loaded / event.total * 100) + "% " + M.util.get_string('uploadprogress', 'atto_recordrtc'));
255 };
256
257 xhr.upload.onerror = function(error) {
258 callback('upload-failed', error);
259 };
260
261 xhr.upload.onabort = function(error) {
262 callback('upload-aborted', error);
263 };
264
265 // POST FormData to PHP script that handles uploading/saving.
266 xhr.open('POST', url);
267 xhr.send(data);
268 },
269
270 // Makes 1min and 2s display as 1:02 on timer instead of 1:2, for example.
271 pad: function(val) {
272 var valString = val + "";
273
274 if (valString.length < 2) {
275 return "0" + valString;
276 } else {
277 return valString;
278 }
279 },
280
281 // Functionality to make recording timer count down.
282 // Also makes recording stop when time limit is hit.
283 set_time: function() {
284 cm.countdownSeconds--;
285
286 cm.startStopBtn.one('span#seconds').set('textContent', cm.pad(cm.countdownSeconds % 60));
287 cm.startStopBtn.one('span#minutes').set('textContent', cm.pad(window.parseInt(cm.countdownSeconds / 60, 10)));
288
289 if (cm.countdownSeconds === 0) {
290 cm.startStopBtn.simulate('click');
291 }
292 },
293
294 // Generates link to recorded annotation to be inserted.
295 create_annotation: function(type, recording_url) {
c8520ca4
MN
296 var html = '';
297 if (type == 'audio') {
298 html = "<audio controls='true'>";
299 } else { // Must be video.
300 html = "<video controls='true'>";
301 }
b4bbf31b 302
c8520ca4
MN
303 html += "<source src='" + recording_url + "'>" + recording_url;
304
305 if (type == 'audio') {
306 html += "</audio>";
307 } else { // Must be video.
308 html += "</video>";
b4bbf31b 309 }
c8520ca4
MN
310
311 return html;
b4bbf31b 312 },
313
314 // Inserts link to annotation in editor text area.
315 insert_annotation: function(type, recording_url) {
316 var annotation = cm.create_annotation(type, recording_url);
317
318 // Insert annotation link.
319 // If user pressed "Cancel", just go back to main recording screen.
320 if (!annotation) {
321 cm.uploadBtn.set('textContent', M.util.get_string('attachrecording', 'atto_recordrtc'));
322 } else {
323 cm.editorScope.setLink(cm.editorScope, annotation);
324 }
325 }
326};
327// This file is part of Moodle - http://moodle.org/
328//
329// Moodle is free software: you can redistribute it and/or modify
330// it under the terms of the GNU General Public License as published by
331// the Free Software Foundation, either version 3 of the License, or
332// (at your option) any later version.
333//
334// Moodle is distributed in the hope that it will be useful,
335// but WITHOUT ANY WARRANTY; without even the implied warranty of
336// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
337// GNU General Public License for more details.
338//
339// You should have received a copy of the GNU General Public License
340// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
341//
342
343/**
344 * Atto recordrtc library functions for checking browser compatibility
345 *
346 * @package atto_recordrtc
347 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
348 * @author Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
349 * @copyright 2017 Blindside Networks Inc.
350 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
351 */
352
353// ESLint directives.
354/* eslint-disable camelcase */
355
356// Scrutinizer CI directives.
357/** global: M */
358
359M.atto_recordrtc = M.atto_recordrtc || {};
360
361// Shorten access to module namespaces.
362var cm = M.atto_recordrtc.commonmodule,
363 am = M.atto_recordrtc.abstractmodule;
364
365M.atto_recordrtc.compatcheckmodule = {
366 // Show alert and close plugin if browser does not support WebRTC at all.
367 check_has_gum: function() {
368 if (!(navigator.mediaDevices && window.MediaRecorder)) {
369 am.show_alert('nowebrtc', function() {
370 cm.editorScope.closeDialogue(cm.editorScope);
371 });
372 }
373 },
374
375 // Notify and redirect user if plugin is used from insecure location.
376 check_secure: function() {
377 var isSecureOrigin = (window.location.protocol === 'https:') ||
378 (window.location.host.indexOf('localhost') !== -1);
379
380 if (!isSecureOrigin) {
381 cm.alertDanger.ancestor().ancestor().removeClass('hide');
b4bbf31b 382 }
383 },
b4bbf31b 384};
385// This file is part of Moodle - http://moodle.org/
386//
387// Moodle is free software: you can redistribute it and/or modify
388// it under the terms of the GNU General Public License as published by
389// the Free Software Foundation, either version 3 of the License, or
390// (at your option) any later version.
391//
392// Moodle is distributed in the hope that it will be useful,
393// but WITHOUT ANY WARRANTY; without even the implied warranty of
394// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
395// GNU General Public License for more details.
396//
397// You should have received a copy of the GNU General Public License
398// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
399//
400
401/**
402 * Atto recordrtc library functions for function abstractions
403 *
404 * @package atto_recordrtc
405 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
406 * @author Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
407 * @copyright 2017 Blindside Networks Inc.
408 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
409 */
410
411// ESLint directives.
412/* eslint-disable camelcase */
413
414// Scrutinizer CI directives.
415/** global: M */
416/** global: Y */
417
418M.atto_recordrtc = M.atto_recordrtc || {};
419
420// Shorten access to module namespaces.
421var cm = M.atto_recordrtc.commonmodule,
422 am = M.atto_recordrtc.abstractmodule;
423
424M.atto_recordrtc.abstractmodule = {
425 // A helper for making a Moodle alert appear.
426 // Subject is the content of the alert (which error ther alert is for).
427 // Possibility to add on-alert-close event.
428 show_alert: function(subject, onCloseEvent) {
429 Y.use('moodle-core-notification-alert', function() {
430 var dialogue = new M.core.alert({
431 title: M.util.get_string(subject + '_title', 'atto_recordrtc'),
432 message: M.util.get_string(subject, 'atto_recordrtc')
433 });
434
435 if (onCloseEvent) {
436 dialogue.after('complete', onCloseEvent);
437 }
438 });
439 },
440
441 // Handle getUserMedia errors.
442 handle_gum_errors: function(error, commonConfig) {
443 var btnLabel = M.util.get_string('recordingfailed', 'atto_recordrtc'),
444 treatAsStopped = function() {
445 commonConfig.onMediaStopped(btnLabel);
446 };
447
448 // Changes 'CertainError' -> 'gumcertain' to match language string names.
449 var stringName = 'gum' + error.name.replace('Error', '').toLowerCase();
450
451 // After alert, proceed to treat as stopped recording, or close dialogue.
452 if (stringName !== 'gumsecurity') {
453 am.show_alert(stringName, treatAsStopped);
454 } else {
455 am.show_alert(stringName, function() {
456 cm.editorScope.closeDialogue(cm.editorScope);
457 });
458 }
459 },
460
461 // Select best options for the recording codec.
462 select_rec_options: function(recType) {
463 var types, options;
464
465 if (recType === 'audio') {
466 types = [
467 'audio/webm;codecs=opus',
468 'audio/ogg;codecs=opus'
469 ];
470 options = {
471 audioBitsPerSecond: window.parseInt(cm.editorScope.get('audiobitrate'))
472 };
473 } else {
474 types = [
475 'video/webm;codecs=vp9,opus',
476 'video/webm;codecs=h264,opus',
477 'video/webm;codecs=vp8,opus'
478 ];
479 options = {
480 audioBitsPerSecond: window.parseInt(cm.editorScope.get('audiobitrate')),
481 videoBitsPerSecond: window.parseInt(cm.editorScope.get('videobitrate'))
482 };
483 }
484
485 var compatTypes = types.filter(function(type) {
486 return window.MediaRecorder.isTypeSupported(type);
487 });
488
489 if (compatTypes.length !== 0) {
490 options.mimeType = compatTypes[0];
491 }
492
493 return options;
494 }
495};
496// This file is part of Moodle - http://moodle.org/
497//
498// Moodle is free software: you can redistribute it and/or modify
499// it under the terms of the GNU General Public License as published by
500// the Free Software Foundation, either version 3 of the License, or
501// (at your option) any later version.
502//
503// Moodle is distributed in the hope that it will be useful,
504// but WITHOUT ANY WARRANTY; without even the implied warranty of
505// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
506// GNU General Public License for more details.
507//
508// You should have received a copy of the GNU General Public License
509// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
510//
511
512/**
513 * Atto recordrtc library functions
514 *
515 * @package atto_recordrtc
516 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
517 * @author Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
518 * @copyright 2017 Blindside Networks Inc.
519 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
520 */
521
522// ESLint directives.
523/* eslint-disable camelcase, spaced-comment */
524
525// Scrutinizer CI directives.
526/** global: M */
527/** global: Y */
528
529M.atto_recordrtc = M.atto_recordrtc || {};
530
531// Shorten access to module namespaces.
532var cm = M.atto_recordrtc.commonmodule,
533 am = M.atto_recordrtc.abstractmodule,
534 ccm = M.atto_recordrtc.compatcheckmodule;
535
536M.atto_recordrtc.audiomodule = {
537 init: function(scope) {
538 // Assignment of global variables.
539 cm.editorScope = scope; // Allows access to the editor's "this" context.
540 cm.alertWarning = Y.one('div#alert-warning');
541 cm.alertDanger = Y.one('div#alert-danger');
542 cm.player = Y.one('audio#player');
543 cm.playerDOM = document.querySelector('audio#player');
544 cm.startStopBtn = Y.one('button#start-stop');
545 cm.uploadBtn = Y.one('button#upload');
546 cm.recType = 'audio';
7dfa238c 547 cm.maxUploadSize = scope.get('maxrecsize');
b4bbf31b 548
549 // Show alert and close plugin if WebRTC is not supported.
550 ccm.check_has_gum();
551 // Show alert and redirect user if connection is not secure.
552 ccm.check_secure();
b4bbf31b 553
554 // Run when user clicks on "record" button.
555 cm.startStopBtn.on('click', function() {
556 cm.startStopBtn.set('disabled', true);
557
558 // If button is displaying "Start Recording" or "Record Again".
559 if ((cm.startStopBtn.get('textContent') === M.util.get_string('startrecording', 'atto_recordrtc')) ||
560 (cm.startStopBtn.get('textContent') === M.util.get_string('recordagain', 'atto_recordrtc')) ||
561 (cm.startStopBtn.get('textContent') === M.util.get_string('recordingfailed', 'atto_recordrtc'))) {
562 // Make sure the audio player and upload button are not shown.
563 cm.player.ancestor().ancestor().addClass('hide');
564 cm.uploadBtn.ancestor().ancestor().addClass('hide');
565
566 // Change look of recording button.
0174e72b 567 cm.startStopBtn.replaceClass('btn-outline-danger', 'btn-danger');
b4bbf31b 568
569 // Empty the array containing the previously recorded chunks.
570 cm.chunks = [];
571 cm.blobSize = 0;
6ffd66f3 572 cm.uploadBtn.detach('click');
b4bbf31b 573
574 // Initialize common configurations.
575 var commonConfig = {
576 // When the stream is captured from the microphone/webcam.
577 onMediaCaptured: function(stream) {
578 // Make audio stream available at a higher level by making it a property of the common module.
579 cm.stream = stream;
580
581 cm.start_recording(cm.recType, cm.stream);
582 },
583
584 // Revert button to "Record Again" when recording is stopped.
585 onMediaStopped: function(btnLabel) {
586 cm.startStopBtn.set('textContent', btnLabel);
587 cm.startStopBtn.set('disabled', false);
0174e72b 588 cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
b4bbf31b 589 },
590
591 // Handle recording errors.
592 onMediaCapturingFailed: function(error) {
593 am.handle_gum_errors(error, commonConfig);
594 }
595 };
596
597 // Capture audio stream from microphone.
598 M.atto_recordrtc.audiomodule.capture_audio(commonConfig);
599 } else { // If button is displaying "Stop Recording".
600 // First of all clears the countdownTicker.
601 window.clearInterval(cm.countdownTicker);
602
603 // Disable "Record Again" button for 1s to allow background processing (closing streams).
604 window.setTimeout(function() {
605 cm.startStopBtn.set('disabled', false);
606 }, 1000);
607
608 // Stop recording.
609 cm.stop_recording(cm.stream);
610
611 // Change button to offer to record again.
612 cm.startStopBtn.set('textContent', M.util.get_string('recordagain', 'atto_recordrtc'));
0174e72b 613 cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
b4bbf31b 614 }
615
616 // Get dialogue centered.
617 cm.editorScope.getDialogue().centered();
618 });
619 },
620
621 // Setup to get audio stream from microphone.
622 capture_audio: function(config) {
623 cm.capture_user_media(
624 // Media constraints.
625 {
626 audio: true
627 },
628
629 // Success callback.
630 function(audioStream) {
631 // Set audio player source to microphone stream.
632 cm.playerDOM.srcObject = audioStream;
633
634 config.onMediaCaptured(audioStream);
635 },
636
637 // Error callback.
638 function(error) {
639 config.onMediaCapturingFailed(error);
640 }
641 );
642 }
643};
644// This file is part of Moodle - http://moodle.org/
645//
646// Moodle is free software: you can redistribute it and/or modify
647// it under the terms of the GNU General Public License as published by
648// the Free Software Foundation, either version 3 of the License, or
649// (at your option) any later version.
650//
651// Moodle is distributed in the hope that it will be useful,
652// but WITHOUT ANY WARRANTY; without even the implied warranty of
653// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
654// GNU General Public License for more details.
655//
656// You should have received a copy of the GNU General Public License
657// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
658//
659
660/**
661 * Atto recordrtc library functions
662 *
663 * @package atto_recordrtc
664 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
665 * @author Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
666 * @copyright 2017 Blindside Networks Inc.
667 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
668 */
669
670// ESLint directives.
671/* eslint-disable camelcase, spaced-comment */
672
673// Scrutinizer CI directives.
674/** global: M */
675/** global: Y */
676
677M.atto_recordrtc = M.atto_recordrtc || {};
678
679// Shorten access to module namespaces.
680var cm = M.atto_recordrtc.commonmodule,
681 am = M.atto_recordrtc.abstractmodule,
682 ccm = M.atto_recordrtc.compatcheckmodule;
683
684M.atto_recordrtc.videomodule = {
685 init: function(scope) {
686 // Assignment of global variables.
687 cm.editorScope = scope; // Allows access to the editor's "this" context.
688 cm.alertWarning = Y.one('div#alert-warning');
689 cm.alertDanger = Y.one('div#alert-danger');
690 cm.player = Y.one('video#player');
691 cm.playerDOM = document.querySelector('video#player');
692 cm.startStopBtn = Y.one('button#start-stop');
693 cm.uploadBtn = Y.one('button#upload');
694 cm.recType = 'video';
7dfa238c 695 cm.maxUploadSize = scope.get('maxrecsize');
b4bbf31b 696
697 // Show alert and close plugin if WebRTC is not supported.
698 ccm.check_has_gum();
699 // Show alert and redirect user if connection is not secure.
700 ccm.check_secure();
b4bbf31b 701
702 // Run when user clicks on "record" button.
703 cm.startStopBtn.on('click', function() {
704 cm.startStopBtn.set('disabled', true);
705
706 // If button is displaying "Start Recording" or "Record Again".
707 if ((cm.startStopBtn.get('textContent') === M.util.get_string('startrecording', 'atto_recordrtc')) ||
708 (cm.startStopBtn.get('textContent') === M.util.get_string('recordagain', 'atto_recordrtc')) ||
709 (cm.startStopBtn.get('textContent') === M.util.get_string('recordingfailed', 'atto_recordrtc'))) {
710 // Make sure the upload button is not shown.
711 cm.uploadBtn.ancestor().ancestor().addClass('hide');
712
713 // Change look of recording button.
0174e72b 714 cm.startStopBtn.replaceClass('btn-outline-danger', 'btn-danger');
b4bbf31b 715
716 // Empty the array containing the previously recorded chunks.
717 cm.chunks = [];
718 cm.blobSize = 0;
6ffd66f3 719 cm.uploadBtn.detach('click');
b4bbf31b 720
721 // Initialize common configurations.
722 var commonConfig = {
723 // When the stream is captured from the microphone/webcam.
724 onMediaCaptured: function(stream) {
725 // Make video stream available at a higher level by making it a property of the common module.
726 cm.stream = stream;
727
728 cm.start_recording(cm.recType, cm.stream);
729 },
730
731 // Revert button to "Record Again" when recording is stopped.
732 onMediaStopped: function(btnLabel) {
733 cm.startStopBtn.set('textContent', btnLabel);
734 cm.startStopBtn.set('disabled', false);
0174e72b 735 cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
b4bbf31b 736 },
737
738 // Handle recording errors.
739 onMediaCapturingFailed: function(error) {
740 am.handle_gum_errors(error, commonConfig);
741 }
742 };
743
744 // Show video tag without controls to view webcam stream.
745 cm.player.ancestor().ancestor().removeClass('hide');
746 cm.player.set('controls', false);
747
748 // Capture audio+video stream from webcam/microphone.
749 M.atto_recordrtc.videomodule.capture_audio_video(commonConfig);
750 } else { // If button is displaying "Stop Recording".
751 // First of all clears the countdownTicker.
752 window.clearInterval(cm.countdownTicker);
753
754 // Disable "Record Again" button for 1s to allow background processing (closing streams).
755 window.setTimeout(function() {
756 cm.startStopBtn.set('disabled', false);
757 }, 1000);
758
759 // Stop recording.
760 cm.stop_recording(cm.stream);
761
762 // Change button to offer to record again.
763 cm.startStopBtn.set('textContent', M.util.get_string('recordagain', 'atto_recordrtc'));
0174e72b 764 cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
b4bbf31b 765 }
766
767 // Get dialogue centered.
768 cm.editorScope.getDialogue().centered();
769 });
770 },
771
772 // Setup to get audio+video stream from microphone/webcam.
773 capture_audio_video: function(config) {
774 cm.capture_user_media(
775 // Media constraints.
776 {
777 audio: true,
778 video: {
779 width: {ideal: 640},
780 height: {ideal: 480}
781 }
782 },
783
784 // Success callback.
785 function(audioVideoStream) {
786 // Set video player source to microphone+webcam stream, and play it back as it's recording.
787 cm.playerDOM.srcObject = audioVideoStream;
788 cm.playerDOM.play();
789
790 config.onMediaCaptured(audioVideoStream);
791 },
792
793 // Error callback.
794 function(error) {
795 config.onMediaCapturingFailed(error);
796 }
797 );
798 }
799};
800
801
802}, '@VERSION@', {"requires": ["moodle-atto_recordrtc-button"]});