Passed
Push — master ( 773db1...5e3db4 )
by Jacob
01:43
created

yui/src/recording/js/helpermodule.js   A

Complexity

Total Complexity 33
Complexity/F 2.2

Size

Lines of Code 206
Function Count 15

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
c 1
b 0
f 0
nc 32
dl 0
loc 206
rs 9.3999
wmc 33
mnd 5
bc 34
fnc 15
bpm 2.2666
cpm 2.2
noi 0

7 Functions

Rating   Name   Duplication   Size   Complexity  
B M.atto_recordrtc.helpermodule.handle_data_available 0 18 5
B M.atto_recordrtc.helpermodule.handle_stop 0 46 1
B M.atto_recordrtc.helpermodule.upload_to_server 0 42 1
B M.atto_recordrtc.helpermodule.make_xmlhttprequest 0 27 1
A M.atto_recordrtc.helpermodule.pad 0 9 2
A M.atto_recordrtc.helpermodule.set_time 0 10 2
B M.atto_recordrtc.helpermodule.start_recording 0 25 1
1
// This file is part of Moodle - http://moodle.org/
2
//
3
// Moodle is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, either version 3 of the License, or
6
// (at your option) any later version.
7
//
8
// Moodle is distributed in the hope that it will be useful,
9
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
// GNU General Public License for more details.
12
//
13
// You should have received a copy of the GNU General Public License
14
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
15
//
16
17
/**
18
 * Atto recordrtc library functions
19
 *
20
 * @package    atto_recordrtc
21
 * @author     Jesus Federico (jesus [at] blindsidenetworks [dt] com)
22
 * @author     Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
23
 * @copyright  2017 Blindside Networks Inc.
24
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25
 */
26
27
// JSHint directives.
28
/*jshint es5: true */
29
/*jshint onevar: false */
30
/*jshint shadow: true */
31
/*global M */
32
33
// Scrutinizer CI directives.
34
/** global: M */
35
36
M.atto_recordrtc = M.atto_recordrtc || {};
37
38
// Shorten access to module namespaces.
39
var cm = M.atto_recordrtc.commonmodule,
40
    hm = M.atto_recordrtc.helpermodule;
41
42
M.atto_recordrtc.helpermodule = {
43
    // Unitialized variables to be used by the other modules.
44
    blobSize: null,
45
    chunks: null,
46
    countdownSeconds: null,
47
    countdownTicker: null,
48
    maxUploadSize: null,
49
50
    // Add chunks of audio/video to array when made available.
51
    handle_data_available: function(event) {
52
        // Size of all recorded data so far.
53
        hm.blobSize += event.data.size;
54
55
        // Push recording slice to array.
56
        // If total size of recording so far exceeds max upload limit, stop recording.
57
        // An extra condition exists to avoid displaying alert twice.
58
        if ((hm.blobSize >= hm.maxUploadSize) && (!window.localStorage.getItem('alerted'))) {
59
            window.localStorage.setItem('alerted', 'true');
60
61
            cm.startStopBtn.simulate('click');
62
            cm.show_alert('nearingmaxsize');
63
        } else if ((hm.blobSize >= hm.maxUploadSize) && (window.localStorage.getItem('alerted') === 'true')) {
64
            window.localStorage.removeItem('alerted');
65
        } else {
66
            hm.chunks.push(event.data);
67
        }
68
    },
69
70
    // Handle recording end.
71
    handle_stop: function() {
72
        // Set source of audio player.
73
        var blob = new window.Blob(hm.chunks, {type: cm.mediaRecorder.mimeType});
74
        cm.player.set('src', window.URL.createObjectURL(blob));
75
76
        // Show audio player with controls enabled, and unmute.
77
        cm.player.set('muted', false);
78
        cm.player.set('controls', true);
79
        cm.player.ancestor().ancestor().removeClass('hide'); // Only audio player is hidden at this point.
80
81
        // Show upload button.
82
        cm.uploadBtn.set('disabled', false);
83
        cm.uploadBtn.set('textContent', M.util.get_string('attachrecording', 'atto_recordrtc'));
84
        cm.uploadBtn.ancestor().ancestor().removeClass('hide');
85
86
        // Handle when upload button is clicked.
87
        cm.uploadBtn.on('click', function() {
88
            // Trigger error if no recording has been made.
89
            if (!cm.player.get('src') || hm.chunks === []) {
90
                cm.show_alert('norecordingfound');
91
            } else {
92
                cm.uploadBtn.set('disabled', true);
93
94
                // Upload recording to server.
95
                hm.upload_to_server(cm.recType, function(progress, fileURLOrError) {
96
                    if (progress === 'ended') { // Insert annotation in text.
97
                        cm.uploadBtn.set('disabled', false);
98
                        cm.insert_annotation(cm.recType, fileURLOrError);
99
                    } else if (progress === 'upload-failed') { // Show error message in upload button.
100
                        cm.uploadBtn.set('disabled', false);
101
                        cm.uploadBtn.set('textContent',
102
                            M.util.get_string('uploadfailed', 'atto_recordrtc') + ' ' + fileURLOrError);
103
                    } else if (progress === 'upload-failed-404') { // 404 error = File too large in Moodle.
104
                        cm.uploadBtn.set('disabled', false);
105
                        cm.uploadBtn.set('textContent', M.util.get_string('uploadfailed404', 'atto_recordrtc'));
106
                    } else if (progress === 'upload-aborted') {
107
                        cm.uploadBtn.set('disabled', false);
108
                        cm.uploadBtn.set('textContent',
109
                            M.util.get_string('uploadaborted', 'atto_recordrtc') + ' ' + fileURLOrError);
110
                    } else {
111
                        cm.uploadBtn.set('textContent', progress);
112
                    }
113
                });
114
            }
115
        });
116
    },
117
118
    // Get everything set up to start recording.
119
    start_recording: function(type, stream) {
120
        // If none of the mime-types are supported, fall back on browser defaults.
121
        var options = cm.best_rec_options(type);
122
        cm.mediaRecorder = new window.MediaRecorder(stream, options);
123
124
        // Initialize MediaRecorder events and start recording.
125
        cm.mediaRecorder.ondataavailable = hm.handle_data_available;
126
        cm.mediaRecorder.onstop = hm.handle_stop;
127
        cm.mediaRecorder.start(1000); // Capture in 1s chunks. Must be set to work with Firefox.
128
129
        // Mute audio, distracting while recording.
130
        cm.player.set('muted', true);
131
132
        // Set recording timer to the time specified in the settings.
133
        hm.countdownSeconds = cm.editorScope.get('timelimit');
134
        hm.countdownSeconds++;
135
        var timerText = M.util.get_string('stoprecording', 'atto_recordrtc');
136
        timerText += ' (<span id="minutes"></span>:<span id="seconds"></span>)';
137
        cm.startStopBtn.setHTML(timerText);
138
        hm.set_time();
139
        hm.countdownTicker = window.setInterval(hm.set_time, 1000);
140
141
        // Make button clickable again, to allow stopping recording.
142
        cm.startStopBtn.set('disabled', false);
143
    },
144
145
    // Upload recorded audio/video to server.
146
    upload_to_server: function(type, callback) {
147
        var xhr = new window.XMLHttpRequest();
148
149
        // Get src media of audio/video tag.
150
        xhr.open('GET', cm.player.get('src'), true);
151
        xhr.responseType = 'blob';
152
153
        xhr.onload = function() {
154
            if (xhr.status === 200) { // If src media was successfully retrieved.
155
                // blob is now the media that the audio/video tag's src pointed to.
156
                var blob = this.response;
157
158
                // Generate filename with random ID and file extension.
159
                var fileName = (Math.random() * 1000).toString().replace('.', '');
160
                if (type === 'audio') {
161
                    fileName += '-audio.ogg';
162
                } else {
163
                    fileName += '-video.webm';
164
                }
165
166
                // Create FormData to send to PHP upload/save script.
167
                var formData = new window.FormData();
168
                formData.append('contextid', cm.editorScope.get('contextid'));
169
                formData.append('sesskey', cm.editorScope.get('sesskey'));
170
                formData.append(type + '-filename', fileName);
171
                formData.append(type + '-blob', blob);
172
173
                // Pass FormData to PHP script using XHR.
174
                hm.make_xmlhttprequest(cm.editorScope.get('recordrtcroot') + 'save.php', formData,
175
                    function(progress, responseText) {
176
                        if (progress === 'upload-ended') {
177
                            var initialURL = cm.editorScope.get('recordrtcroot') + 'uploads.php/';
178
                            return callback('ended', initialURL + responseText);
179
                        }
180
                        return callback(progress);
181
                    }
182
                );
183
            }
184
        };
185
186
        xhr.send();
187
    },
188
189
    // Handle XHR sending/receiving/status.
190
    make_xmlhttprequest: function(url, data, callback) {
191
        var xhr = new window.XMLHttpRequest();
192
193
        xhr.onreadystatechange = function() {
194
            if ((xhr.readyState === 4) && (xhr.status === 200)) { // When request is finished and successful.
195
                callback('upload-ended', xhr.responseText);
196
            } else if (xhr.status === 404) { // When request returns 404 Not Found.
197
                callback('upload-failed-404');
198
            }
199
        };
200
201
        xhr.upload.onprogress = function(event) {
202
            callback(Math.round(event.loaded / event.total * 100) + "% " + M.util.get_string('uploadprogress', 'atto_recordrtc'));
203
        };
204
205
        xhr.upload.onerror = function(error) {
206
            callback('upload-failed', error);
207
        };
208
209
        xhr.upload.onabort = function(error) {
210
            callback('upload-aborted', error);
211
        };
212
213
        // POST FormData to PHP script that handles uploading/saving.
214
        xhr.open('POST', url);
215
        xhr.send(data);
216
    },
217
218
    // Makes 1min and 2s display as 1:02 on timer instead of 1:2, for example.
219
    pad: function(val) {
220
        var valString = val + "";
221
222
        if (valString.length < 2) {
223
            return "0" + valString;
224
        } else {
225
            return valString;
226
        }
227
    },
228
229
    // Functionality to make recording timer count down.
230
    // Also makes recording stop when time limit is hit.
231
    set_time: function() {
232
        hm.countdownSeconds--;
233
234
        cm.startStopBtn.one('span#seconds').set('textContent', hm.pad(hm.countdownSeconds % 60));
235
        cm.startStopBtn.one('span#minutes').set('textContent', hm.pad(window.parseInt(hm.countdownSeconds / 60, 10)));
236
237
        if (hm.countdownSeconds === 0) {
238
            cm.startStopBtn.simulate('click');
239
        }
240
    }
241
};
242