MDL-60848 atto_recordrtc: Add recordrtc to core
[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,
64 olderMoodle: null,
65 maxUploadSize: null,
66
67 // Capture webcam/microphone stream.
68 capture_user_media: function(mediaConstraints, successCallback, errorCallback) {
69 window.navigator.mediaDevices.getUserMedia(mediaConstraints).then(successCallback).catch(errorCallback);
70 },
71
72 // Add chunks of audio/video to array when made available.
73 handle_data_available: function(event) {
74 // Push recording slice to array.
75 cm.chunks.push(event.data);
76 // Size of all recorded data so far.
77 cm.blobSize += event.data.size;
78
79 // If total size of recording so far exceeds max upload limit, stop recording.
80 // An extra condition exists to avoid displaying alert twice.
81 if (cm.blobSize >= cm.maxUploadSize) {
82 if (!window.localStorage.getItem('alerted')) {
83 window.localStorage.setItem('alerted', 'true');
84
85 cm.startStopBtn.simulate('click');
86 am.show_alert('nearingmaxsize');
87 } else {
88 window.localStorage.removeItem('alerted');
89 }
90
91 cm.chunks.pop();
92 }
93 },
94
95 // Handle recording end.
96 handle_stop: function() {
97 // Set source of audio player.
98 var blob = new window.Blob(cm.chunks, {type: cm.mediaRecorder.mimeType});
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) {
296 var linkText = window.prompt(M.util.get_string('annotationprompt', 'atto_recordrtc'),
297 M.util.get_string('annotation:' + type, 'atto_recordrtc'));
298
299 // Return HTML for annotation link, if user did not press "Cancel".
300 if (!linkText) {
301 return undefined;
302 } else {
303 var annotation = '<a target="_blank" href="' + recording_url + '">' + linkText + '</a>';
304 return annotation;
305 }
306 },
307
308 // Inserts link to annotation in editor text area.
309 insert_annotation: function(type, recording_url) {
310 var annotation = cm.create_annotation(type, recording_url);
311
312 // Insert annotation link.
313 // If user pressed "Cancel", just go back to main recording screen.
314 if (!annotation) {
315 cm.uploadBtn.set('textContent', M.util.get_string('attachrecording', 'atto_recordrtc'));
316 } else {
317 cm.editorScope.setLink(cm.editorScope, annotation);
318 }
319 }
320};
321// This file is part of Moodle - http://moodle.org/
322//
323// Moodle is free software: you can redistribute it and/or modify
324// it under the terms of the GNU General Public License as published by
325// the Free Software Foundation, either version 3 of the License, or
326// (at your option) any later version.
327//
328// Moodle is distributed in the hope that it will be useful,
329// but WITHOUT ANY WARRANTY; without even the implied warranty of
330// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
331// GNU General Public License for more details.
332//
333// You should have received a copy of the GNU General Public License
334// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
335//
336
337/**
338 * Atto recordrtc library functions for checking browser compatibility
339 *
340 * @package atto_recordrtc
341 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
342 * @author Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
343 * @copyright 2017 Blindside Networks Inc.
344 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
345 */
346
347// ESLint directives.
348/* eslint-disable camelcase */
349
350// Scrutinizer CI directives.
351/** global: M */
352
353M.atto_recordrtc = M.atto_recordrtc || {};
354
355// Shorten access to module namespaces.
356var cm = M.atto_recordrtc.commonmodule,
357 am = M.atto_recordrtc.abstractmodule;
358
359M.atto_recordrtc.compatcheckmodule = {
360 // Show alert and close plugin if browser does not support WebRTC at all.
361 check_has_gum: function() {
362 if (!(navigator.mediaDevices && window.MediaRecorder)) {
363 am.show_alert('nowebrtc', function() {
364 cm.editorScope.closeDialogue(cm.editorScope);
365 });
366 }
367 },
368
369 // Notify and redirect user if plugin is used from insecure location.
370 check_secure: function() {
371 var isSecureOrigin = (window.location.protocol === 'https:') ||
372 (window.location.host.indexOf('localhost') !== -1);
373
374 if (!isSecureOrigin) {
375 cm.alertDanger.ancestor().ancestor().removeClass('hide');
376
377 if (window.bowser.chrome || window.bowser.opera) {
378 am.show_alert('gumsecurity', function() {
379 cm.editorScope.closeDialogue(cm.editorScope);
380 });
381 }
382 }
383 },
384
385 // Display "consider switching browsers" message if not using:
386 // - Firefox 29+;
387 // - Chrome 49+;
388 // - Opera 36+.
389 check_browser: function() {
390 if (!((window.bowser.firefox && window.bowser.version >= 29) ||
391 (window.bowser.chrome && window.bowser.version >= 49) ||
392 (window.bowser.opera && window.bowser.version >= 36))) {
393 cm.alertWarning.ancestor().ancestor().removeClass('hide');
394 }
395 }
396};
397// This file is part of Moodle - http://moodle.org/
398//
399// Moodle is free software: you can redistribute it and/or modify
400// it under the terms of the GNU General Public License as published by
401// the Free Software Foundation, either version 3 of the License, or
402// (at your option) any later version.
403//
404// Moodle is distributed in the hope that it will be useful,
405// but WITHOUT ANY WARRANTY; without even the implied warranty of
406// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
407// GNU General Public License for more details.
408//
409// You should have received a copy of the GNU General Public License
410// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
411//
412
413/**
414 * Atto recordrtc library functions for function abstractions
415 *
416 * @package atto_recordrtc
417 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
418 * @author Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
419 * @copyright 2017 Blindside Networks Inc.
420 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
421 */
422
423// ESLint directives.
424/* eslint-disable camelcase */
425
426// Scrutinizer CI directives.
427/** global: M */
428/** global: Y */
429
430M.atto_recordrtc = M.atto_recordrtc || {};
431
432// Shorten access to module namespaces.
433var cm = M.atto_recordrtc.commonmodule,
434 am = M.atto_recordrtc.abstractmodule;
435
436M.atto_recordrtc.abstractmodule = {
437 // A helper for making a Moodle alert appear.
438 // Subject is the content of the alert (which error ther alert is for).
439 // Possibility to add on-alert-close event.
440 show_alert: function(subject, onCloseEvent) {
441 Y.use('moodle-core-notification-alert', function() {
442 var dialogue = new M.core.alert({
443 title: M.util.get_string(subject + '_title', 'atto_recordrtc'),
444 message: M.util.get_string(subject, 'atto_recordrtc')
445 });
446
447 if (onCloseEvent) {
448 dialogue.after('complete', onCloseEvent);
449 }
450 });
451 },
452
453 // Handle getUserMedia errors.
454 handle_gum_errors: function(error, commonConfig) {
455 var btnLabel = M.util.get_string('recordingfailed', 'atto_recordrtc'),
456 treatAsStopped = function() {
457 commonConfig.onMediaStopped(btnLabel);
458 };
459
460 // Changes 'CertainError' -> 'gumcertain' to match language string names.
461 var stringName = 'gum' + error.name.replace('Error', '').toLowerCase();
462
463 // After alert, proceed to treat as stopped recording, or close dialogue.
464 if (stringName !== 'gumsecurity') {
465 am.show_alert(stringName, treatAsStopped);
466 } else {
467 am.show_alert(stringName, function() {
468 cm.editorScope.closeDialogue(cm.editorScope);
469 });
470 }
471 },
472
473 // Select best options for the recording codec.
474 select_rec_options: function(recType) {
475 var types, options;
476
477 if (recType === 'audio') {
478 types = [
479 'audio/webm;codecs=opus',
480 'audio/ogg;codecs=opus'
481 ];
482 options = {
483 audioBitsPerSecond: window.parseInt(cm.editorScope.get('audiobitrate'))
484 };
485 } else {
486 types = [
487 'video/webm;codecs=vp9,opus',
488 'video/webm;codecs=h264,opus',
489 'video/webm;codecs=vp8,opus'
490 ];
491 options = {
492 audioBitsPerSecond: window.parseInt(cm.editorScope.get('audiobitrate')),
493 videoBitsPerSecond: window.parseInt(cm.editorScope.get('videobitrate'))
494 };
495 }
496
497 var compatTypes = types.filter(function(type) {
498 return window.MediaRecorder.isTypeSupported(type);
499 });
500
501 if (compatTypes.length !== 0) {
502 options.mimeType = compatTypes[0];
503 }
504
505 return options;
506 }
507};
508// This file is part of Moodle - http://moodle.org/
509//
510// Moodle is free software: you can redistribute it and/or modify
511// it under the terms of the GNU General Public License as published by
512// the Free Software Foundation, either version 3 of the License, or
513// (at your option) any later version.
514//
515// Moodle is distributed in the hope that it will be useful,
516// but WITHOUT ANY WARRANTY; without even the implied warranty of
517// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
518// GNU General Public License for more details.
519//
520// You should have received a copy of the GNU General Public License
521// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
522//
523
524/**
525 * Atto recordrtc library functions
526 *
527 * @package atto_recordrtc
528 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
529 * @author Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
530 * @copyright 2017 Blindside Networks Inc.
531 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
532 */
533
534// ESLint directives.
535/* eslint-disable camelcase, spaced-comment */
536
537// Scrutinizer CI directives.
538/** global: M */
539/** global: Y */
540
541M.atto_recordrtc = M.atto_recordrtc || {};
542
543// Shorten access to module namespaces.
544var cm = M.atto_recordrtc.commonmodule,
545 am = M.atto_recordrtc.abstractmodule,
546 ccm = M.atto_recordrtc.compatcheckmodule;
547
548M.atto_recordrtc.audiomodule = {
549 init: function(scope) {
550 // Assignment of global variables.
551 cm.editorScope = scope; // Allows access to the editor's "this" context.
552 cm.alertWarning = Y.one('div#alert-warning');
553 cm.alertDanger = Y.one('div#alert-danger');
554 cm.player = Y.one('audio#player');
555 cm.playerDOM = document.querySelector('audio#player');
556 cm.startStopBtn = Y.one('button#start-stop');
557 cm.uploadBtn = Y.one('button#upload');
558 cm.recType = 'audio';
559 cm.olderMoodle = scope.get('oldermoodle');
560 // Extract the numbers from the string, and convert to bytes.
561 cm.maxUploadSize = window.parseInt(scope.get('maxrecsize').match(/\d+/)[0], 10) * Math.pow(1024, 2);
562
563 // Show alert and close plugin if WebRTC is not supported.
564 ccm.check_has_gum();
565 // Show alert and redirect user if connection is not secure.
566 ccm.check_secure();
567 // Show alert if using non-ideal browser.
568 ccm.check_browser();
569
570 // Run when user clicks on "record" button.
571 cm.startStopBtn.on('click', function() {
572 cm.startStopBtn.set('disabled', true);
573
574 // If button is displaying "Start Recording" or "Record Again".
575 if ((cm.startStopBtn.get('textContent') === M.util.get_string('startrecording', 'atto_recordrtc')) ||
576 (cm.startStopBtn.get('textContent') === M.util.get_string('recordagain', 'atto_recordrtc')) ||
577 (cm.startStopBtn.get('textContent') === M.util.get_string('recordingfailed', 'atto_recordrtc'))) {
578 // Make sure the audio player and upload button are not shown.
579 cm.player.ancestor().ancestor().addClass('hide');
580 cm.uploadBtn.ancestor().ancestor().addClass('hide');
581
582 // Change look of recording button.
583 if (!cm.olderMoodle) {
584 cm.startStopBtn.replaceClass('btn-outline-danger', 'btn-danger');
585 }
586
587 // Empty the array containing the previously recorded chunks.
588 cm.chunks = [];
589 cm.blobSize = 0;
590
591 // Initialize common configurations.
592 var commonConfig = {
593 // When the stream is captured from the microphone/webcam.
594 onMediaCaptured: function(stream) {
595 // Make audio stream available at a higher level by making it a property of the common module.
596 cm.stream = stream;
597
598 cm.start_recording(cm.recType, cm.stream);
599 },
600
601 // Revert button to "Record Again" when recording is stopped.
602 onMediaStopped: function(btnLabel) {
603 cm.startStopBtn.set('textContent', btnLabel);
604 cm.startStopBtn.set('disabled', false);
605 if (!cm.olderMoodle) {
606 cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
607 }
608 },
609
610 // Handle recording errors.
611 onMediaCapturingFailed: function(error) {
612 am.handle_gum_errors(error, commonConfig);
613 }
614 };
615
616 // Capture audio stream from microphone.
617 M.atto_recordrtc.audiomodule.capture_audio(commonConfig);
618 } else { // If button is displaying "Stop Recording".
619 // First of all clears the countdownTicker.
620 window.clearInterval(cm.countdownTicker);
621
622 // Disable "Record Again" button for 1s to allow background processing (closing streams).
623 window.setTimeout(function() {
624 cm.startStopBtn.set('disabled', false);
625 }, 1000);
626
627 // Stop recording.
628 cm.stop_recording(cm.stream);
629
630 // Change button to offer to record again.
631 cm.startStopBtn.set('textContent', M.util.get_string('recordagain', 'atto_recordrtc'));
632 if (!cm.olderMoodle) {
633 cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
634 }
635 }
636
637 // Get dialogue centered.
638 cm.editorScope.getDialogue().centered();
639 });
640 },
641
642 // Setup to get audio stream from microphone.
643 capture_audio: function(config) {
644 cm.capture_user_media(
645 // Media constraints.
646 {
647 audio: true
648 },
649
650 // Success callback.
651 function(audioStream) {
652 // Set audio player source to microphone stream.
653 cm.playerDOM.srcObject = audioStream;
654
655 config.onMediaCaptured(audioStream);
656 },
657
658 // Error callback.
659 function(error) {
660 config.onMediaCapturingFailed(error);
661 }
662 );
663 }
664};
665// This file is part of Moodle - http://moodle.org/
666//
667// Moodle is free software: you can redistribute it and/or modify
668// it under the terms of the GNU General Public License as published by
669// the Free Software Foundation, either version 3 of the License, or
670// (at your option) any later version.
671//
672// Moodle is distributed in the hope that it will be useful,
673// but WITHOUT ANY WARRANTY; without even the implied warranty of
674// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
675// GNU General Public License for more details.
676//
677// You should have received a copy of the GNU General Public License
678// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
679//
680
681/**
682 * Atto recordrtc library functions
683 *
684 * @package atto_recordrtc
685 * @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
686 * @author Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
687 * @copyright 2017 Blindside Networks Inc.
688 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
689 */
690
691// ESLint directives.
692/* eslint-disable camelcase, spaced-comment */
693
694// Scrutinizer CI directives.
695/** global: M */
696/** global: Y */
697
698M.atto_recordrtc = M.atto_recordrtc || {};
699
700// Shorten access to module namespaces.
701var cm = M.atto_recordrtc.commonmodule,
702 am = M.atto_recordrtc.abstractmodule,
703 ccm = M.atto_recordrtc.compatcheckmodule;
704
705M.atto_recordrtc.videomodule = {
706 init: function(scope) {
707 // Assignment of global variables.
708 cm.editorScope = scope; // Allows access to the editor's "this" context.
709 cm.alertWarning = Y.one('div#alert-warning');
710 cm.alertDanger = Y.one('div#alert-danger');
711 cm.player = Y.one('video#player');
712 cm.playerDOM = document.querySelector('video#player');
713 cm.startStopBtn = Y.one('button#start-stop');
714 cm.uploadBtn = Y.one('button#upload');
715 cm.recType = 'video';
716 cm.olderMoodle = scope.get('oldermoodle');
717 // Extract the numbers from the string, and convert to bytes.
718 cm.maxUploadSize = window.parseInt(scope.get('maxrecsize').match(/\d+/)[0], 10) * Math.pow(1024, 2);
719
720 // Show alert and close plugin if WebRTC is not supported.
721 ccm.check_has_gum();
722 // Show alert and redirect user if connection is not secure.
723 ccm.check_secure();
724 // Show alert if using non-ideal browser.
725 ccm.check_browser();
726
727 // Run when user clicks on "record" button.
728 cm.startStopBtn.on('click', function() {
729 cm.startStopBtn.set('disabled', true);
730
731 // If button is displaying "Start Recording" or "Record Again".
732 if ((cm.startStopBtn.get('textContent') === M.util.get_string('startrecording', 'atto_recordrtc')) ||
733 (cm.startStopBtn.get('textContent') === M.util.get_string('recordagain', 'atto_recordrtc')) ||
734 (cm.startStopBtn.get('textContent') === M.util.get_string('recordingfailed', 'atto_recordrtc'))) {
735 // Make sure the upload button is not shown.
736 cm.uploadBtn.ancestor().ancestor().addClass('hide');
737
738 // Change look of recording button.
739 if (!cm.olderMoodle) {
740 cm.startStopBtn.replaceClass('btn-outline-danger', 'btn-danger');
741 }
742
743 // Empty the array containing the previously recorded chunks.
744 cm.chunks = [];
745 cm.blobSize = 0;
746
747 // Initialize common configurations.
748 var commonConfig = {
749 // When the stream is captured from the microphone/webcam.
750 onMediaCaptured: function(stream) {
751 // Make video stream available at a higher level by making it a property of the common module.
752 cm.stream = stream;
753
754 cm.start_recording(cm.recType, cm.stream);
755 },
756
757 // Revert button to "Record Again" when recording is stopped.
758 onMediaStopped: function(btnLabel) {
759 cm.startStopBtn.set('textContent', btnLabel);
760 cm.startStopBtn.set('disabled', false);
761 if (!cm.olderMoodle) {
762 cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
763 }
764 },
765
766 // Handle recording errors.
767 onMediaCapturingFailed: function(error) {
768 am.handle_gum_errors(error, commonConfig);
769 }
770 };
771
772 // Show video tag without controls to view webcam stream.
773 cm.player.ancestor().ancestor().removeClass('hide');
774 cm.player.set('controls', false);
775
776 // Capture audio+video stream from webcam/microphone.
777 M.atto_recordrtc.videomodule.capture_audio_video(commonConfig);
778 } else { // If button is displaying "Stop Recording".
779 // First of all clears the countdownTicker.
780 window.clearInterval(cm.countdownTicker);
781
782 // Disable "Record Again" button for 1s to allow background processing (closing streams).
783 window.setTimeout(function() {
784 cm.startStopBtn.set('disabled', false);
785 }, 1000);
786
787 // Stop recording.
788 cm.stop_recording(cm.stream);
789
790 // Change button to offer to record again.
791 cm.startStopBtn.set('textContent', M.util.get_string('recordagain', 'atto_recordrtc'));
792 if (!cm.olderMoodle) {
793 cm.startStopBtn.replaceClass('btn-danger', 'btn-outline-danger');
794 }
795 }
796
797 // Get dialogue centered.
798 cm.editorScope.getDialogue().centered();
799 });
800 },
801
802 // Setup to get audio+video stream from microphone/webcam.
803 capture_audio_video: function(config) {
804 cm.capture_user_media(
805 // Media constraints.
806 {
807 audio: true,
808 video: {
809 width: {ideal: 640},
810 height: {ideal: 480}
811 }
812 },
813
814 // Success callback.
815 function(audioVideoStream) {
816 // Set video player source to microphone+webcam stream, and play it back as it's recording.
817 cm.playerDOM.srcObject = audioVideoStream;
818 cm.playerDOM.play();
819
820 config.onMediaCaptured(audioVideoStream);
821 },
822
823 // Error callback.
824 function(error) {
825 config.onMediaCapturingFailed(error);
826 }
827 );
828 }
829};
830
831
832}, '@VERSION@', {"requires": ["moodle-atto_recordrtc-button"]});