tinymce/js/commonmodule.js   B
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
nc 4
nop 0
dl 0
loc 21
rs 8.7624
c 1
b 0
f 0

1 Function

Rating   Name   Duplication   Size   Complexity  
A commonmodule.js ➔ d 0 3 1
1
// TinyMCE recordrtc library functions.
2
// @package    tinymce_recordrtc.
3
// @author     Jesus Federico  (jesus [at] blindsidenetworks [dt] com).
4
// @author     Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
5
// @copyright  2016 onwards, Blindside Networks Inc.
6
// @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
7
8
// ESLint directives.
9
/* global tinyMCE, tinyMCEPopup */
10
/* exported alertWarning, alertDanger, countdownTicker, playerDOM */
11
/* eslint-disable camelcase, no-alert */
12
13
// Scrutinizer CI directives.
14
/** global: navigator */
15
/** global: parent */
16
/** global: M */
17
/** global: Y */
18
/** global: tinyMCEPopup */
19
20
M.tinymce_recordrtc = M.tinymce_recordrtc || {};
21
22
// Extract plugin settings to params hash.
23
(function() {
24
    var params = {};
25
    var r = /([^&=]+)=?([^&]*)/g;
26
27
    var d = function(s) {
28
        return window.decodeURIComponent(s.replace(/\+/g, ' '));
29
    };
30
31
    var search = window.location.search;
32
    var match = r.exec(search.substring(1));
33
    while (match) {
34
        params[d(match[1])] = d(match[2]);
35
36
        if (d(match[2]) === 'true' || d(match[2]) === 'false') {
37
            params[d(match[1])] = d(match[2]) === 'true' ? true : false;
38
        }
39
        match = r.exec(search.substring(1));
40
    }
41
42
    window.params = params;
43
})();
44
45
// Initialize some variables.
46
var alertWarning = null;
47
var alertDanger = null;
48
var blobSize = null;
49
var chunks = null;
50
var countdownSeconds = null;
51
var countdownTicker = null;
52
var maxUploadSize = null;
53
var mediaRecorder = null;
54
var player = null;
55
var playerDOM = null;
56
var recType = null;
57
var startStopBtn = null;
58
var uploadBtn = null;
59
60
// Capture webcam/microphone stream.
61
M.tinymce_recordrtc.capture_user_media = function(mediaConstraints, successCallback, errorCallback) {
62
    window.navigator.mediaDevices.getUserMedia(mediaConstraints).then(successCallback).catch(errorCallback);
63
};
64
65
// Add chunks of audio/video to array when made available.
66
M.tinymce_recordrtc.handle_data_available = function(event) {
67
    // Push recording slice to array.
68
    chunks.push(event.data);
69
    // Size of all recorded data so far.
70
    blobSize += event.data.size;
71
72
    // If total size of recording so far exceeds max upload limit, stop recording.
73
    // An extra condition exists to avoid displaying alert twice.
74
    if (blobSize >= maxUploadSize) {
75
        if (!window.localStorage.getItem('alerted')) {
76
            window.localStorage.setItem('alerted', 'true');
77
78
            Y.use('node-event-simulate', function() {
79
                startStopBtn.simulate('click');
80
            });
81
            M.tinymce_recordrtc.show_alert('nearingmaxsize');
82
        } else {
83
            window.localStorage.removeItem('alerted');
84
        }
85
86
        chunks.pop();
87
    }
88
};
89
90
M.tinymce_recordrtc.handle_stop = function() {
91
    // Set source of audio player.
92
    var blob = new window.Blob(chunks, {type: mediaRecorder.mimeType});
93
    player.set('src', window.URL.createObjectURL(blob));
94
95
    // Show audio player with controls enabled, and unmute.
96
    player.set('muted', false);
97
    player.set('controls', true);
98
    player.ancestor().ancestor().removeClass('hide');
99
100
    // Show upload button.
101
    uploadBtn.ancestor().ancestor().removeClass('hide');
102
    uploadBtn.set('textContent', M.util.get_string('attachrecording', 'tinymce_recordrtc'));
103
    uploadBtn.set('disabled', false);
104
105
    // Handle when upload button is clicked.
106
    uploadBtn.on('click', function() {
107
        // Trigger error if no recording has been made.
108
        if (chunks.length === 0) {
109
            M.tinymce_recordrtc.show_alert('norecordingfound');
110
        } else {
111
            uploadBtn.set('disabled', true);
112
113
            // Upload recording to server.
114
            M.tinymce_recordrtc.upload_to_server(recType, function(progress, fileURLOrError) {
115
                if (progress === 'ended') { // Insert annotation in text.
116
                    uploadBtn.set('disabled', false);
117
                    M.tinymce_recordrtc.insert_annotation(recType, fileURLOrError);
118
                } else if (progress === 'upload-failed') { // Show error message in upload button.
119
                    uploadBtn.set('disabled', false);
120
                    uploadBtn.set('textContent', M.util.get_string('uploadfailed', 'tinymce_recordrtc') + ' ' + fileURLOrError);
121
                } else if (progress === 'upload-failed-404') { // 404 error = File too large in Moodle.
122
                    uploadBtn.set('disabled', false);
123
                    uploadBtn.set('textContent', M.util.get_string('uploadfailed404', 'tinymce_recordrtc'));
124
                } else if (progress === 'upload-aborted') {
125
                    uploadBtn.set('disabled', false);
126
                    uploadBtn.set('textContent', M.util.get_string('uploadaborted', 'tinymce_recordrtc') + ' ' + fileURLOrError);
127
                } else {
128
                    uploadBtn.set('textContent', progress);
129
                }
130
            });
131
        }
132
    });
133
};
134
135
// Get everything set up to start recording.
136
M.tinymce_recordrtc.start_recording = function(type, stream) {
137
    // The options for the recording codecs and bitrates.
138
    var options = M.tinymce_recordrtc.select_rec_options(type);
139
    mediaRecorder = new window.MediaRecorder(stream, options);
140
141
    // Initialize MediaRecorder events and start recording.
142
    mediaRecorder.ondataavailable = M.tinymce_recordrtc.handle_data_available;
143
    mediaRecorder.onstop = M.tinymce_recordrtc.handle_stop;
144
    mediaRecorder.start(1000); // Capture in 1s chunks. Must be set to work with Firefox.
145
146
    // Mute audio, distracting while recording.
147
    player.set('muted', true);
148
149
    // Set recording timer to the time specified in the settings.
150
    countdownSeconds = window.params.timelimit;
151
    countdownSeconds++;
152
    var timerText = M.util.get_string('stoprecording', 'tinymce_recordrtc');
153
    timerText += ' (<span id="minutes"></span>:<span id="seconds"></span>)';
154
    startStopBtn.setHTML(timerText);
155
    M.tinymce_recordrtc.set_time();
156
    countdownTicker = window.setInterval(M.tinymce_recordrtc.set_time, 1000);
157
158
    // Make button clickable again, to allow stopping recording.
159
    startStopBtn.set('disabled', false);
160
};
161
162
M.tinymce_recordrtc.stop_recording = function(stream) {
163
    // Stop recording microphone stream.
164
    mediaRecorder.stop();
165
166
    // Stop each individual MediaTrack.
167
    var tracks = stream.getTracks();
168
    for (var i = 0; i < tracks.length; i++) {
169
        tracks[i].stop();
170
    }
171
};
172
173
// Upload recorded audio/video to server.
174
M.tinymce_recordrtc.upload_to_server = function(type, callback) {
175
    var xhr = new window.XMLHttpRequest();
176
177
    // Get src media of audio/video tag.
178
    xhr.open('GET', player.get('src'), true);
179
    xhr.responseType = 'blob';
180
181
    xhr.onload = function() {
182
        if (xhr.status === 200) { // If src media was successfully retrieved.
183
            // blob is now the media that the audio/video tag's src pointed to.
184
            var blob = this.response;
185
186
            // Generate filename with random ID and file extension.
187
            var fileName = (Math.random() * 1000).toString().replace('.', '');
188
            fileName += (type === 'audio') ? '-audio.ogg'
189
                                           : '-video.webm';
190
191
            // Create FormData to send to PHP filepicker-upload script.
192
            var formData = new window.FormData(),
193
                editorId = tinyMCE.activeEditor.id,
194
                filepickerOptions = parent.M.editor_tinymce.filepicker_options[editorId].link,
195
                repositoryKeys = window.Object.keys(filepickerOptions.repositories);
196
197
            formData.append('repo_upload_file', blob, fileName);
198
            formData.append('itemid', filepickerOptions.itemid);
199
200
            for (var i = 0; i < repositoryKeys.length; i++) {
201
                if (filepickerOptions.repositories[repositoryKeys[i]].type === 'upload') {
202
                    formData.append('repo_id', filepickerOptions.repositories[repositoryKeys[i]].id);
203
                    break;
204
                }
205
            }
206
207
            formData.append('env', filepickerOptions.env);
208
            formData.append('sesskey', M.cfg.sesskey);
209
            formData.append('client_id', filepickerOptions.client_id);
210
            formData.append('savepath', '/');
211
            formData.append('ctx_id', filepickerOptions.context.id);
212
213
            // Pass FormData to PHP script using XHR.
214
            var uploadEndpoint = M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload';
215
            M.tinymce_recordrtc.make_xmlhttprequest(uploadEndpoint, formData, function(progress, responseText) {
216
                if (progress === 'upload-ended') {
217
                    callback('ended', window.JSON.parse(responseText).url);
218
                } else {
219
                    callback(progress);
220
                }
221
            });
222
        }
223
    };
224
225
    xhr.send();
226
};
227
228
// Handle XHR sending/receiving/status.
229
M.tinymce_recordrtc.make_xmlhttprequest = function(url, data, callback) {
230
    var xhr = new window.XMLHttpRequest();
231
232
    xhr.onreadystatechange = function() {
233
        if ((xhr.readyState === 4) && (xhr.status === 200)) { // When request is finished and successful.
234
            callback('upload-ended', xhr.responseText);
235
        } else if (xhr.status === 404) { // When request returns 404 Not Found.
236
            callback('upload-failed-404');
237
        }
238
    };
239
240
    xhr.upload.onprogress = function(event) {
241
        callback(Math.round(event.loaded / event.total * 100) + "% " + M.util.get_string('uploadprogress', 'tinymce_recordrtc'));
242
    };
243
244
    xhr.upload.onerror = function(error) {
245
        callback('upload-failed', error);
246
    };
247
248
    xhr.upload.onabort = function(error) {
249
        callback('upload-aborted', error);
250
    };
251
252
    // POST FormData to PHP script that handles uploading/saving.
253
    xhr.open('POST', url);
254
    xhr.send(data);
255
};
256
257
// Makes 1min and 2s display as 1:02 on timer instead of 1:2, for example.
258
M.tinymce_recordrtc.pad = function(val) {
259
    var valString = val + "";
260
261
    if (valString.length < 2) {
262
        return "0" + valString;
263
    } else {
264
        return valString;
265
    }
266
};
267
268
// Functionality to make recording timer count down.
269
// Also makes recording stop when time limit is hit.
270
M.tinymce_recordrtc.set_time = function() {
271
    countdownSeconds--;
272
273
    startStopBtn.one('span#seconds').set('textContent', M.tinymce_recordrtc.pad(countdownSeconds % 60));
274
    startStopBtn.one('span#minutes').set('textContent', M.tinymce_recordrtc.pad(window.parseInt(countdownSeconds / 60, 10)));
275
276
    if (countdownSeconds === 0) {
277
        Y.use('node-event-simulate', function() {
278
            startStopBtn.simulate('click');
279
        });
280
    }
281
};
282
283
// Generates link to recorded annotation to be inserted.
284
M.tinymce_recordrtc.create_annotation = function(type, recording_url) {
285
    var linkText = window.prompt(M.util.get_string('annotationprompt', 'tinymce_recordrtc'),
286
                                 M.util.get_string('annotation:' + type, 'tinymce_recordrtc'));
287
288
    // Return HTML for annotation link, if user did not press "Cancel".
289
    if (!linkText) {
290
        return undefined;
291
    } else {
292
        var annotation = '<div><a target="_blank" href="' + recording_url + '">' + linkText + '</a></div>';
293
        return annotation;
294
    }
295
};
296
297
// Inserts link to annotation in editor text area.
298
M.tinymce_recordrtc.insert_annotation = function(type, recording_url) {
299
    var annotation = M.tinymce_recordrtc.create_annotation(type, recording_url);
300
301
    // Insert annotation link.
302
    // If user pressed "Cancel", just go back to main recording screen.
303
    if (!annotation) {
304
        uploadBtn.set('textContent', M.util.get_string('attachrecording', 'tinymce_recordrtc'));
305
    } else {
306
        tinyMCEPopup.editor.execCommand('mceInsertContent', false, annotation);
307
        tinyMCEPopup.close();
308
    }
309
};
310