Completed
Push — master ( 58bfde...3a56b6 )
by Jacob
01:33
created

M.tinymce_recordrtc.handleDataAvailable   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 5
c 4
b 0
f 0
nc 3
nop 1
dl 0
loc 17
rs 8.8571
1
// TinyMCE recordrtc library functions.
2
// @package    tinymce_recordrtc.
3
// @author     Jesus Federico  (jesus [at] blindsidenetworks [dt] com).
4
// @copyright  2016 to present, Blindside Networks Inc.
5
// @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later.
6
7
/** global: M */
8
/** global: tinyMCEPopup */
9
/** global: bowser */
10
/** global: params */
11
/** global: recordrtc */
12
13
M.tinymce_recordrtc = M.tinymce_recordrtc || {};
14
15
// Extract plugin settings to params hash.
16
(function() {
17
    var params = {};
18
    var r = /([^&=]+)=?([^&]*)/g;
19
20
    var d = function(s) {
21
        return decodeURIComponent(s.replace(/\+/g, ' '));
22
    };
23
24
    var search = window.location.search;
25
    var match = r.exec(search.substring(1));
26
    while (match) {
27
        params[d(match[1])] = d(match[2]);
28
29
        if (d(match[2]) === 'true' || d(match[2]) === 'false') {
30
            params[d(match[1])] = d(match[2]) === 'true' ? true : false;
31
        }
32
        match = r.exec(search.substring(1));
33
    }
34
35
    window.params = params;
36
})();
37
38
// Initialize some variables.
39
var player = null;
40
var startStopBtn = null;
41
var uploadBtn = null;
42
var countdownSeconds = null;
43
var countdownTicker = null;
44
var recType = null;
45
var mediaRecorder = null;
46
var chunks = null;
47
var blobSize = null;
48
var maxUploadSize = null;
49
50
// Notify and redirect user if plugin is used from insecure location.
51
M.tinymce_recordrtc.check_secure = function() {
52
    var isSecureOrigin = (window.location.protocol === 'https:') ||
53
                         (window.location.host.indexOf('localhost') !== -1);
54
55
    if (!isSecureOrigin) {
56
        window.alert(M.util.get_string('insecurealert', 'tinymce_recordrtc'));
57
        tinyMCEPopup.close();
58
    }
59
}
60
61
// Display "consider switching browsers" message if not using:
62
// - Firefox 29+;
63
// - Chrome 49+;
64
// - Opera 36+.
65
M.tinymce_recordrtc.check_browser = function() {
66
    if (!((bowser.firefox && bowser.version >= 29) ||
67
          (bowser.chrome && bowser.version >= 49) ||
68
          (bowser.opera && bowser.version >= 36))) {
69
        var alert = document.querySelector('div[id=alert-warning]');
70
        alert.parentElement.parentElement.classList.remove('hide');
71
    }
72
};
73
74
// Capture webcam/microphone stream.
75
M.tinymce_recordrtc.captureUserMedia = function(mediaConstraints, successCallback, errorCallback) {
76
    navigator.mediaDevices.getUserMedia(mediaConstraints).then(successCallback).catch(errorCallback);
77
};
78
79
// Add chunks of audio/video to array when made available.
80
M.tinymce_recordrtc.handleDataAvailable = function(event) {
81
    // Size of all recorded data so far.
82
    blobSize += event.data.size;
83
84
    // Push recording slice to array.
85
    // If total size of recording so far exceeds max upload limit, stop recording.
86
    if ((blobSize >= maxUploadSize) && (!localStorage.getItem('alerted'))) {
1 ignored issue
show
Bug introduced by
The variable localStorage seems to be never declared. If this is a global, consider adding a /** global: localStorage */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
87
        localStorage.setItem('alerted', 'true');
88
89
        startStopBtn.click();
90
        window.alert(M.util.get_string('nearingmaxsize', 'tinymce_recordrtc'));
91
    } else if ((blobSize >= maxUploadSize) && (localStorage.getItem('alerted') === 'true')) {
92
        localStorage.removeItem('alerted');
93
    } else {
94
        chunks.push(event.data);
95
    }
96
};
97
98
// Get everything set up to start recording.
99
M.tinymce_recordrtc.startRecording = function(type, stream) {
100
    // The options for the recording codecs and bitrates.
101
    var options = null;
102
    if (type === 'audio') {
103
        if (MediaRecorder.isTypeSupported('audio/webm;codecs=opus')) {
1 ignored issue
show
Bug introduced by
The variable MediaRecorder seems to be never declared. If this is a global, consider adding a /** global: MediaRecorder */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
104
            options = {
105
                audioBitsPerSecond: params['audiobitrate'],
106
                mimeType: 'audio/webm;codecs=opus'
107
            };
108
        } else if (MediaRecorder.isTypeSupported('audio/ogg;codecs=opus')) {
109
            options = {
110
                audioBitsPerSecond: params['audiobitrate'],
111
                mimeType: 'audio/ogg;codecs=opus'
112
            };
113
        }
114
    } else {
115
        if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9,opus')) {
116
            options = {
117
                audioBitsPerSecond: params['audiobitrate'],
118
                videoBitsPerSecond: params['videobitrate'],
119
                mimeType: 'video/webm;codecs=vp9,opus'
120
            };
121
        } else if (MediaRecorder.isTypeSupported('video/webm;codecs=h264,opus')) {
122
            options = {
123
                audioBitsPerSecond: params['audiobitrate'],
124
                videoBitsPerSecond: params['videobitrate'],
125
                mimeType: 'video/webm;codecs=h264,opus'
126
            };
127
        } else if (MediaRecorder.isTypeSupported('video/webm;codecs=vp8,opus')) {
128
            options = {
129
                audioBitsPerSecond: params['audiobitrate'],
130
                videoBitsPerSecond: params['videobitrate'],
131
                mimeType: 'video/webm;codecs=vp8,opus'
132
            };
133
        }
134
    }
135
136
    // If none of the options above are supported, fall back on browser defaults.
137
    mediaRecorder = options ? new MediaRecorder(stream)
138
                            : new MediaRecorder(stream, options);
139
140
    // Initialize MediaRecorder events and start recording.
141
    mediaRecorder.ondataavailable = M.tinymce_recordrtc.handleDataAvailable;
142
    mediaRecorder.start(1000); // Capture in 10ms chunks. Must be set to work with Firefox.
143
144
    // Mute audio, distracting while recording.
145
    player.muted = true;
146
147
    // Set recording timer to the time specified in the settings.
148
    countdownSeconds = params['timelimit'];
149
    countdownSeconds++;
150
    startStopBtn.innerHTML = M.util.get_string('stoprecording', 'tinymce_recordrtc') +
151
                             ' (<span id="minutes"></span>:<span id="seconds"></span>)';
152
    M.tinymce_recordrtc.setTime();
153
    countdownTicker = setInterval(M.tinymce_recordrtc.setTime, 1000);
154
155
    // Make button clickable again, to allow stopping recording.
156
    startStopBtn.disabled = false;
157
};
158
159
// Upload recorded audio/video to server.
160
M.tinymce_recordrtc.uploadToServer = function(type, callback) {
161
    var xhr = new XMLHttpRequest();
162
163
    // Get src media of audio/video tag.
164
    xhr.open('GET', player.src, true);
165
    xhr.responseType = 'blob';
166
167
    xhr.onload = function() {
168
        if (xhr.status === 200) { // If src media was successfully retrieved.
169
            // blob is now the media that the audio/video tag's src pointed to.
170
            var blob = this.response;
171
172
            // Generate filename with random ID and file extension.
173
            var fileName = (Math.random() * 1000).toString().replace('.', '');
174
            if (type === 'audio') {
175
              fileName += '-audio.ogg';
176
            } else {
177
              fileName += '-video.webm';
178
            }
179
180
            // Create FormData to send to PHP upload/save script.
181
            var formData = new FormData();
182
            formData.append('contextid', recordrtc.contextid);
183
            formData.append('sesskey', parent.M.cfg.sesskey);
184
            formData.append(type + '-filename', fileName);
185
            formData.append(type + '-blob', blob);
186
187
            // Pass FormData to PHP script using XHR.
188
            M.tinymce_recordrtc.makeXMLHttpRequest('save.php', formData, function(progress, responseText) {
189
                if (progress === 'upload-ended') {
190
                    var initialURL = location.href.replace(location.href.split('/').pop(), '') + 'uploads.php/';
191
                    callback('ended', initialURL + responseText);
192
                } else {
193
                    callback(progress);
194
                }
195
            });
196
        }
197
    };
198
199
    xhr.send();
200
};
201
202
// Handle XHR sending/receiving/status.
203
M.tinymce_recordrtc.makeXMLHttpRequest = function(url, data, callback) {
204
    var xhr = new XMLHttpRequest();
205
206
    xhr.onreadystatechange = function() {
207
        if ((xhr.readyState === 4) && (xhr.status === 200)) { // When request is finished and successful.
208
            callback('upload-ended', xhr.responseText);
209
        } else if (xhr.status === 404) { // When request returns 404 Not Found.
210
            callback('upload-failed-404');
211
        }
212
    };
213
214
    xhr.upload.onprogress = function(event) {
215
        callback(Math.round(event.loaded / event.total * 100) + "% " +
216
                 M.util.get_string('uploadprogress', 'tinymce_recordrtc'));
217
    };
218
219
    xhr.upload.onerror = function(error) {
220
        callback('upload-failed', error);
221
    };
222
223
    xhr.upload.onabort = function(error) {
224
        callback('upload-aborted', error);
225
    };
226
227
    // POST FormData to PHP script that handles uploading/saving.
228
    xhr.open('POST', url);
229
    xhr.send(data);
230
};
231
232
// Makes 1min and 2s display as 1:02 on timer instead of 1:2, for example.
233
M.tinymce_recordrtc.pad = function(val) {
234
    var valString = val + "";
235
236
    if (valString.length < 2) {
237
        return "0" + valString;
238
    } else {
239
        return valString;
240
    }
241
};
242
243
// Functionality to make recording timer count down.
244
// Also makes recording stop when time limit is hit.
245
M.tinymce_recordrtc.setTime = function() {
246
    countdownSeconds--;
247
248
    startStopBtn.querySelector('span#seconds').textContent = M.tinymce_recordrtc.pad(countdownSeconds % 60);
249
    startStopBtn.querySelector('span#minutes').textContent = M.tinymce_recordrtc.pad(parseInt(countdownSeconds / 60));
250
251
    if (countdownSeconds === 0) {
252
        startStopBtn.click();
253
    }
254
};
255
256
// Generates link to recorded annotation to be inserted.
257
M.tinymce_recordrtc.create_annotation = function(type, recording_url) {
258
    var linkText = window.prompt(M.util.get_string('annotationprompt', 'tinymce_recordrtc'),
259
                                 M.util.get_string('annotation:' + type, 'tinymce_recordrtc'));
260
261
    // Return HTML for annotation link.
262
    // If user pressed "Cancel", just go back to main recording screen.
263
    if (!linkText) {
264
        return undefined;
265
    } else {
266
        var annotation = '<div id="recordrtc_annotation" class="text-center"><a target="_blank" href="' + recording_url + '">' + linkText + '</a></div>';
267
        return annotation;
268
    }
269
};
270
271
// Inserts link to annotation in editor text area.
272
M.tinymce_recordrtc.insert_annotation = function(type, recording_url) {
273
    var annotation = M.tinymce_recordrtc.create_annotation(type, recording_url);
274
275
    // Insert annotation link.
276
    // If user pressed "Cancel", just go back to main recording screen.
277
    if (!annotation) {
278
        uploadBtn.textContent = M.util.get_string('attachrecording', 'tinymce_recordrtc');
279
        return undefined;
280
    } else {
281
        tinyMCEPopup.editor.execCommand('mceInsertContent', false, annotation);
282
        tinyMCEPopup.close();
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
283
    }
284
};
285