AutorÃa | Ultima modificación | Ver Log |
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
//
/**
* Atto recordrtc library functions
*
* @package atto_recordrtc
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
* @author Jacob Prud'homme (jacob [dt] prudhomme [at] blindsidenetworks [dt] com)
* @copyright 2017 Blindside Networks Inc.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// ESLint directives.
/* eslint-disable camelcase, no-alert, spaced-comment */
// JSHint directives.
/*global M */
/*jshint es5: true */
/*jshint onevar: false */
/*jshint shadow: true */
// Scrutinizer CI directives.
/** global: M */
/** global: Y */
M.atto_recordrtc = M.atto_recordrtc || {};
// Shorten access to M.atto_recordrtc.commonmodule namespace.
var cm = M.atto_recordrtc.commonmodule,
am = M.atto_recordrtc.abstractmodule;
M.atto_recordrtc.commonmodule = {
// Unitialized variables to be used by the other modules.
editorScope: null,
alertWarning: null,
alertDanger: null,
player: null,
playerDOM: null, // Used to manipulate DOM directly.
startStopBtn: null,
uploadBtn: null,
countdownSeconds: null,
countdownTicker: null,
recType: null,
stream: null,
mediaRecorder: null,
chunks: null,
blobSize: null,
maxUploadSize: null,
// Capture webcam/microphone stream.
capture_user_media: function(mediaConstraints, successCallback, errorCallback) {
window.navigator.mediaDevices.getUserMedia(mediaConstraints).then(successCallback).catch(errorCallback);
},
// Add chunks of audio/video to array when made available.
handle_data_available: function(event) {
// Push recording slice to array.
cm.chunks.push(event.data);
// Size of all recorded data so far.
cm.blobSize += event.data.size;
// If total size of recording so far exceeds max upload limit, stop recording.
// An extra condition exists to avoid displaying alert twice.
if (cm.blobSize >= cm.maxUploadSize) {
if (!window.localStorage.getItem('alerted')) {
window.localStorage.setItem('alerted', 'true');
cm.startStopBtn.simulate('click');
am.show_alert('nearingmaxsize');
} else {
window.localStorage.removeItem('alerted');
}
cm.chunks.pop();
}
},
// Handle recording end.
handle_stop: function() {
// Set source of audio player.
var blob = new window.Blob(cm.chunks, {type: cm.mediaRecorder.mimeType});
cm.player.set('srcObject', null);
cm.player.set('src', window.URL.createObjectURL(blob));
// Show audio player with controls enabled, and unmute.
cm.player.set('muted', false);
cm.player.set('controls', true);
cm.player.ancestor().ancestor().removeClass('hide');
// Show upload button.
cm.uploadBtn.ancestor().ancestor().removeClass('hide');
cm.uploadBtn.set('textContent', M.util.get_string('attachrecording', 'atto_recordrtc'));
cm.uploadBtn.set('disabled', false);
// Get dialogue centered.
cm.editorScope.getDialogue().centered();
// Handle when upload button is clicked.
cm.uploadBtn.on('click', function() {
// Trigger error if no recording has been made.
if (cm.chunks.length === 0) {
am.show_alert('norecordingfound');
} else {
cm.uploadBtn.set('disabled', true);
// Upload recording to server.
cm.upload_to_server(cm.recType, function(progress, fileURLOrError) {
if (progress === 'ended') { // Insert annotation in text.
cm.uploadBtn.set('disabled', false);
cm.insert_annotation(cm.recType, fileURLOrError);
} else if (progress === 'upload-failed') { // Show error message in upload button.
cm.uploadBtn.set('disabled', false);
cm.uploadBtn.set('textContent',
M.util.get_string('uploadfailed', 'atto_recordrtc') + ' ' + fileURLOrError);
} else if (progress === 'upload-failed-404') { // 404 error = File too large in Moodle.
cm.uploadBtn.set('disabled', false);
cm.uploadBtn.set('textContent', M.util.get_string('uploadfailed404', 'atto_recordrtc'));
} else if (progress === 'upload-aborted') {
cm.uploadBtn.set('disabled', false);
cm.uploadBtn.set('textContent',
M.util.get_string('uploadaborted', 'atto_recordrtc') + ' ' + fileURLOrError);
} else {
cm.uploadBtn.set('textContent', progress);
}
});
}
});
},
// Get everything set up to start recording.
start_recording: function(type, stream) {
// The options for the recording codecs and bitrates.
var options = am.select_rec_options(type);
cm.mediaRecorder = new window.MediaRecorder(stream, options);
// Initialize MediaRecorder events and start recording.
cm.mediaRecorder.ondataavailable = cm.handle_data_available;
cm.mediaRecorder.onstop = cm.handle_stop;
cm.mediaRecorder.start(1000); // Capture in 1s chunks. Must be set to work with Firefox.
// Mute audio, distracting while recording.
cm.player.set('muted', true);
// Set recording timer to the time specified in the settings.
if (type === 'audio') {
cm.countdownSeconds = cm.editorScope.get('audiotimelimit');
} else if (type === 'video') {
cm.countdownSeconds = cm.editorScope.get('videotimelimit');
} else {
// Default timer.
cm.countdownSeconds = cm.editorScope.get('defaulttimelimit');
}
cm.countdownSeconds++;
var timerText = M.util.get_string('stoprecording', 'atto_recordrtc');
timerText += ' (<span id="minutes"></span>:<span id="seconds"></span>)';
cm.startStopBtn.setHTML(timerText);
cm.set_time();
cm.countdownTicker = window.setInterval(cm.set_time, 1000);
// Make button clickable again, to allow stopping recording.
cm.startStopBtn.set('disabled', false);
},
// Get everything set up to stop recording.
stop_recording: function(stream) {
// Stop recording stream.
cm.mediaRecorder.stop();
// Stop each individual MediaTrack.
var tracks = stream.getTracks();
for (var i = 0; i < tracks.length; i++) {
tracks[i].stop();
}
},
getFileExtension: function(type) {
if (type === 'audio') {
if (window.MediaRecorder.isTypeSupported('audio/ogg')) {
return 'ogg';
} else if (window.MediaRecorder.isTypeSupported('audio/mp4')) {
return 'mp4';
}
} else {
if (window.MediaRecorder.isTypeSupported('audio/webm')) {
return 'webm';
} else if (window.MediaRecorder.isTypeSupported('audio/mp4')) {
return 'mp4';
}
}
window.console.warn('Unknown file type for MediaRecorder API');
return '';
},
// Upload recorded audio/video to server.
upload_to_server: function(type, callback) {
var xhr = new window.XMLHttpRequest();
var fileExtension = this.getFileExtension(type);
// Get src media of audio/video tag.
xhr.open('GET', cm.player.get('src'), true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status === 200) { // If src media was successfully retrieved.
// blob is now the media that the audio/video tag's src pointed to.
var blob = this.response;
// Generate filename with random ID and file extension.
var fileName = (Math.random() * 1000).toString().replace('.', '');
fileName += (type === 'audio') ? '-audio.' + fileExtension
: '-video.' + fileExtension;
// Create FormData to send to PHP filepicker-upload script.
var formData = new window.FormData(),
filepickerOptions = cm.editorScope.get('host').get('filepickeroptions').link,
repositoryKeys = window.Object.keys(filepickerOptions.repositories);
formData.append('repo_upload_file', blob, fileName);
formData.append('itemid', filepickerOptions.itemid);
for (var i = 0; i < repositoryKeys.length; i++) {
if (filepickerOptions.repositories[repositoryKeys[i]].type === 'upload') {
formData.append('repo_id', filepickerOptions.repositories[repositoryKeys[i]].id);
break;
}
}
formData.append('env', filepickerOptions.env);
formData.append('sesskey', M.cfg.sesskey);
formData.append('client_id', filepickerOptions.client_id);
formData.append('savepath', '/');
formData.append('ctx_id', filepickerOptions.context.id);
// Pass FormData to PHP script using XHR.
var uploadEndpoint = M.cfg.wwwroot + '/repository/repository_ajax.php?action=upload';
cm.make_xmlhttprequest(uploadEndpoint, formData,
function(progress, responseText) {
if (progress === 'upload-ended') {
callback('ended', window.JSON.parse(responseText).url);
} else {
callback(progress);
}
}
);
}
};
xhr.send();
},
// Handle XHR sending/receiving/status.
make_xmlhttprequest: function(url, data, callback) {
var xhr = new window.XMLHttpRequest();
xhr.onreadystatechange = function() {
if ((xhr.readyState === 4) && (xhr.status === 200)) { // When request is finished and successful.
callback('upload-ended', xhr.responseText);
} else if (xhr.status === 404) { // When request returns 404 Not Found.
callback('upload-failed-404');
}
};
xhr.upload.onprogress = function(event) {
callback(Math.round(event.loaded / event.total * 100) + "% " + M.util.get_string('uploadprogress', 'atto_recordrtc'));
};
xhr.upload.onerror = function(error) {
callback('upload-failed', error);
};
xhr.upload.onabort = function(error) {
callback('upload-aborted', error);
};
// POST FormData to PHP script that handles uploading/saving.
xhr.open('POST', url);
xhr.send(data);
},
// Makes 1min and 2s display as 1:02 on timer instead of 1:2, for example.
pad: function(val) {
var valString = val + "";
if (valString.length < 2) {
return "0" + valString;
} else {
return valString;
}
},
// Functionality to make recording timer count down.
// Also makes recording stop when time limit is hit.
set_time: function() {
cm.countdownSeconds--;
cm.startStopBtn.one('span#seconds').set('textContent', cm.pad(cm.countdownSeconds % 60));
cm.startStopBtn.one('span#minutes').set('textContent', cm.pad(window.parseInt(cm.countdownSeconds / 60, 10)));
if (cm.countdownSeconds === 0) {
cm.startStopBtn.simulate('click');
}
},
// Generates link to recorded annotation to be inserted.
create_annotation: function(type, recording_url) {
var html = '';
if (type == 'audio') {
html = "<audio controls='true'>";
} else { // Must be video.
html = "<video controls='true'>";
}
html += "<source src='" + recording_url + "'>" + recording_url;
if (type == 'audio') {
html += "</audio>";
} else { // Must be video.
html += "</video>";
}
return html;
},
// Inserts link to annotation in editor text area.
insert_annotation: function(type, recording_url) {
var annotation = cm.create_annotation(type, recording_url);
// Insert annotation link.
// If user pressed "Cancel", just go back to main recording screen.
if (!annotation) {
cm.uploadBtn.set('textContent', M.util.get_string('attachrecording', 'atto_recordrtc'));
} else {
cm.editorScope.setLink(cm.editorScope, annotation);
}
}
};