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
1 YUI.add('moodle-atto_recordrtc-recording', function (Y, NAME) {
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 //
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  */
29 // ESLint directives.
30 /* eslint-disable camelcase, no-alert, spaced-comment */
32 // JSHint directives.
33 /*global M */
34 /*jshint es5: true */
35 /*jshint onevar: false */
36 /*jshint shadow: true */
38 // Scrutinizer CI directives.
39 /** global: M */
40 /** global: Y */
42 M.atto_recordrtc = M.atto_recordrtc || {};
44 // Shorten access to M.atto_recordrtc.commonmodule namespace.
45 var cm = M.atto_recordrtc.commonmodule,
46     am = M.atto_recordrtc.abstractmodule;
48 M.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,
64     maxUploadSize: null,
66     // Capture webcam/microphone stream.
67     capture_user_media: function(mediaConstraints, successCallback, errorCallback) {
68         window.navigator.mediaDevices.getUserMedia(mediaConstraints).then(successCallback).catch(errorCallback);
69     },
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;
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');
84                 cm.startStopBtn.simulate('click');
85                 am.show_alert('nearingmaxsize');
86             } else {
87                 window.localStorage.removeItem('alerted');
88             }
90             cm.chunks.pop();
91         }
92     },
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});
98         cm.player.set('srcObject', null);
99         cm.player.set('src', window.URL.createObjectURL(blob));
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');
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);
111         // Get dialogue centered.
112         cm.editorScope.getDialogue().centered();
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);
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     },
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);
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.
157         // Mute audio, distracting while recording.
158         cm.player.set('muted', true);
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);
169         // Make button clickable again, to allow stopping recording.
170         cm.startStopBtn.set('disabled', false);
171     },
173     // Get everything set up to stop recording.
174     stop_recording: function(stream) {
175         // Stop recording stream.
176         cm.mediaRecorder.stop();
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     },
185     // Upload recorded audio/video to server.
186     upload_to_server: function(type, callback) {
187         var xhr = new window.XMLHttpRequest();
189         // Get src media of audio/video tag.
190         xhr.open('GET', cm.player.get('src'), true);
191         xhr.responseType = 'blob';
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;
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';
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);
208                 formData.append('repo_upload_file', blob, fileName);
209                 formData.append('itemid', filepickerOptions.itemid);
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                 }
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);
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         };
238         xhr.send();
239     },
241     // Handle XHR sending/receiving/status.
242     make_xmlhttprequest: function(url, data, callback) {
243         var xhr = new window.XMLHttpRequest();
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         };
253         xhr.upload.onprogress = function(event) {
254             callback(Math.round(event.loaded / event.total * 100) + "% " + M.util.get_string('uploadprogress', 'atto_recordrtc'));
255         };
257         xhr.upload.onerror = function(error) {
258             callback('upload-failed', error);
259         };
261         xhr.upload.onabort = function(error) {
262             callback('upload-aborted', error);
263         };
265         // POST FormData to PHP script that handles uploading/saving.
266         xhr.open('POST', url);
267         xhr.send(data);
268     },
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 + "";
274         if (valString.length < 2) {
275             return "0" + valString;
276         } else {
277             return valString;
278         }
279     },
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--;
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)));
289         if (cm.countdownSeconds === 0) {
290             cm.startStopBtn.simulate('click');
291         }
292     },
294     // Generates link to recorded annotation to be inserted.
295     create_annotation: function(type, recording_url) {
296         var html = '';
297         if (type == 'audio') {
298             html = "<audio controls='true'>";
299         } else { // Must be video.
300             html = "<video controls='true'>";
301         }
303         html += "<source src='" + recording_url + "'>" + recording_url;
305         if (type == 'audio') {
306             html += "</audio>";
307         } else { // Must be video.
308             html += "</video>";
309         }
311         return html;
312     },
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);
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 //
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  */
353 // ESLint directives.
354 /* eslint-disable camelcase */
356 // Scrutinizer CI directives.
357 /** global: M */
359 M.atto_recordrtc = M.atto_recordrtc || {};
361 // Shorten access to module namespaces.
362 var cm = M.atto_recordrtc.commonmodule,
363     am = M.atto_recordrtc.abstractmodule;
365 M.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     },
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);
380         if (!isSecureOrigin) {
381             cm.alertDanger.ancestor().ancestor().removeClass('hide');
382         }
383     },
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 //
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  */
411 // ESLint directives.
412 /* eslint-disable camelcase */
414 // Scrutinizer CI directives.
415 /** global: M */
416 /** global: Y */
418 M.atto_recordrtc = M.atto_recordrtc || {};
420 // Shorten access to module namespaces.
421 var cm = M.atto_recordrtc.commonmodule,
422     am = M.atto_recordrtc.abstractmodule;
424 M.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             });
435             if (onCloseEvent) {
436                 dialogue.after('complete', onCloseEvent);
437             }
438         });
439     },
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             };
448         // Changes 'CertainError' -> 'gumcertain' to match language string names.
449         var stringName = 'gum' + error.name.replace('Error', '').toLowerCase();
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     },
461     // Select best options for the recording codec.
462     select_rec_options: function(recType) {
463         var types, options;
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         }
485         var compatTypes = types.filter(function(type) {
486             return window.MediaRecorder.isTypeSupported(type);
487         });
489         if (compatTypes.length !== 0) {
490             options.mimeType = compatTypes[0];
491         }
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 //
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  */
522 // ESLint directives.
523 /* eslint-disable camelcase, spaced-comment */
525 // Scrutinizer CI directives.
526 /** global: M */
527 /** global: Y */
529 M.atto_recordrtc = M.atto_recordrtc || {};
531 // Shorten access to module namespaces.
532 var cm = M.atto_recordrtc.commonmodule,
533     am = M.atto_recordrtc.abstractmodule,
534     ccm = M.atto_recordrtc.compatcheckmodule;
536 M.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';
547         cm.maxUploadSize = scope.get('maxrecsize');
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();
554         // Run when user clicks on "record" button.
555         cm.startStopBtn.on('click', function() {
556             cm.startStopBtn.set('disabled', true);
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');
566                 // Change look of recording button.
567                 cm.startStopBtn.replaceClass('btn-outline-danger', 'btn-danger');
569                 // Empty the array containing the previously recorded chunks.
570                 cm.chunks = [];
571                 cm.blobSize = 0;
572                 cm.uploadBtn.detach('click');
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;
581                         cm.start_recording(cm.recType, cm.stream);
582                     },
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);
588                         cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
589                     },
591                     // Handle recording errors.
592                     onMediaCapturingFailed: function(error) {
593                         am.handle_gum_errors(error, commonConfig);
594                     }
595                 };
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);
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);
608                 // Stop recording.
609                 cm.stop_recording(cm.stream);
611                 // Change button to offer to record again.
612                 cm.startStopBtn.set('textContent', M.util.get_string('recordagain', 'atto_recordrtc'));
613                 cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
614             }
616             // Get dialogue centered.
617             cm.editorScope.getDialogue().centered();
618         });
619     },
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             },
629             // Success callback.
630             function(audioStream) {
631                 // Set audio player source to microphone stream.
632                 cm.playerDOM.srcObject = audioStream;
634                 config.onMediaCaptured(audioStream);
635             },
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 //
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  */
670 // ESLint directives.
671 /* eslint-disable camelcase, spaced-comment */
673 // Scrutinizer CI directives.
674 /** global: M */
675 /** global: Y */
677 M.atto_recordrtc = M.atto_recordrtc || {};
679 // Shorten access to module namespaces.
680 var cm = M.atto_recordrtc.commonmodule,
681     am = M.atto_recordrtc.abstractmodule,
682     ccm = M.atto_recordrtc.compatcheckmodule;
684 M.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';
695         cm.maxUploadSize = scope.get('maxrecsize');
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();
702         // Run when user clicks on "record" button.
703         cm.startStopBtn.on('click', function() {
704             cm.startStopBtn.set('disabled', true);
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');
713                 // Change look of recording button.
714                 cm.startStopBtn.replaceClass('btn-outline-danger', 'btn-danger');
716                 // Empty the array containing the previously recorded chunks.
717                 cm.chunks = [];
718                 cm.blobSize = 0;
719                 cm.uploadBtn.detach('click');
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;
728                         cm.start_recording(cm.recType, cm.stream);
729                     },
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);
735                         cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
736                     },
738                     // Handle recording errors.
739                     onMediaCapturingFailed: function(error) {
740                         am.handle_gum_errors(error, commonConfig);
741                     }
742                 };
744                 // Show video tag without controls to view webcam stream.
745                 cm.player.ancestor().ancestor().removeClass('hide');
746                 cm.player.set('controls', false);
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);
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);
759                 // Stop recording.
760                 cm.stop_recording(cm.stream);
762                 // Change button to offer to record again.
763                 cm.startStopBtn.set('textContent', M.util.get_string('recordagain', 'atto_recordrtc'));
764                 cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
765             }
767             // Get dialogue centered.
768             cm.editorScope.getDialogue().centered();
769         });
770     },
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             },
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();
790                 config.onMediaCaptured(audioVideoStream);
791             },
793             // Error callback.
794             function(error) {
795                 config.onMediaCapturingFailed(error);
796             }
797         );
798     }
799 };
802 }, '@VERSION@', {"requires": ["moodle-atto_recordrtc-button"]});