/**
* File : media/EventSoundsElement.js
* Created : 01/04/2015
* By : Francesc Busquets <francesc@gmail.com>
*
* JClic.js
* An HTML5 player of JClic activities
* https://projectestac.github.io/jclic.js
*
* @source https://github.com/projectestac/jclic.js
*
* @license EUPL-1.2
* @licstart
* (c) 2000-2020 Educational Telematic Network of Catalonia (XTEC)
*
* Licensed under the EUPL, Version 1.1 or -as soon they will be approved by
* the European Commission- subsequent versions of the EUPL (the "Licence");
* You may not use this work except in compliance with the Licence.
*
* You may obtain a copy of the Licence at:
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the Licence is distributed on an "AS IS" basis, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* Licence for the specific language governing permissions and limitations
* under the Licence.
* @licend
* @module
*/
/* global navigator, window, document, Blob, URL, MediaRecorder */
import { log } from '../Utils.js';
/**
* The AudioBuffer object provides sound recording and replaying to activities.
*/
export class AudioBuffer {
/**
* AudioBuffer constructor
* @param {number} [seconds] - The maximum amount of time allowed to be recorded by this AudioBuffer
*/
constructor(seconds) {
if (navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia)
this.enabled = true;
if (seconds)
this.seconds = seconds;
this.chunks = [];
}
/**
* Starts playing the currently recorded audio, if any.
*/
play() {
this.stop();
if (this.mediaPlayer) {
this.mediaPlayer.currentTime = 0;
this.mediaPlayer.play();
} else {
this.playWhenFinished = true;
}
}
/**
* Stops the current operation, either recording or playing audio
*/
stop() {
if (this.mediaRecorder && this.mediaRecorder.state === 'recording')
this.mediaRecorder.stop();
else if (this.mediaPlayer && !this.mediaPlayer.paused)
this.mediaPlayer.pause();
}
/**
* Starts recording audio, or stops the recording if already started.
* @param {external:jQuery} [$div] - Optional `div` element where the recording is performed, as a jQuery ref.
*/
record($div) {
if (this.mediaRecorder && this.mediaRecorder.state === 'recording')
this.mediaRecorder.stop();
else if (this.enabled) {
this.stop();
this.mediaPlayer = null;
navigator.mediaDevices.getUserMedia({ audio: true, video: false })
.then(mediaStream => {
this.mediaRecorder = new MediaRecorder(mediaStream);
this.mediaRecorder.ondataavailable = ev => this.chunks.push(ev.data);
this.mediaRecorder.onerror = err => {
log('error', `Error recording audio: ${err}`);
this.mediaRecorder = null;
};
this.mediaRecorder.onstart = () => {
log('debug', 'Recording audio started');
this.visualFeedbak(true, $div);
};
this.mediaRecorder.onstop = () => {
log('debug', 'Recording audio finished');
this.visualFeedbak(false, $div);
if (this.timeoutID) {
window.clearTimeout(this.timeoutID);
this.timeoutID = null;
}
const options = {};
if (this.chunks.length > 0 && this.chunks[0].type)
options.type = this.chunks[0].type;
const blob = new Blob(this.chunks, options);
this.chunks = [];
this.mediaPlayer = document.createElement('audio');
this.mediaPlayer.src = URL.createObjectURL(blob);
this.mediaPlayer.pause();
this.mediaRecorder = null;
if (this.playWhenFinished) {
this.playWhenFinished = false;
this.mediaPlayer.play();
}
};
this.mediaRecorder.onwarning = ev => log('warn', `Warning recording audio: ${ev}`);
this.playWhenFinished = false;
this.mediaRecorder.start();
this.timeoutID = window.setTimeout(() => {
if (this.mediaRecorder);
this.mediaRecorder.stop();
}, this.seconds * 1000);
})
.catch(err => {
log('error', err.toString());
this.visualFeedbak(false, $div);
});
}
}
/**
* Set visual feedback to the user while the system is recording audio
* Currently changes the cursor pointer associated to the HTML element
* containing the recorder.
* @param {boolean} enabled - Flag indicating if the visual feedback should be active or inactive
* @param {external:jQuery} [$div] - Optional `div` element where the recording is performed, as a jQuery ref.
*/
visualFeedbak(enabled, $div) {
if ($div)
$div.css('cursor', enabled ? 'progress' : 'inherit');
}
/**
* Clears all data associated to this AudioBuffer
*/
clear() {
this.stop();
this.mediaPlayer = null;
}
}
Object.assign(AudioBuffer.prototype, {
/**
* AudioBuffer is enabled only in browsers with `navigator.MediaDevices.getuserMedia`
* @name module:media/AudioBuffer.AudioBuffer#enabled
* @type {boolean}
*/
enabled: false,
/**
* Maximum length of recordings allowed to this AudioBuffer (in seconds)
* @name module:media/AudioBuffer.AudioBuffer#seconds
* @type {number}
*/
seconds: 20,
/**
* The object used to record audio data and convert it to a valid stream for the {@link module:media/ActiveMediaPlayer.ActiveMediaPlayer ActiveMediaPlayer}
* @name module:media/AudioBuffer.AudioBuffer#mediaRecorder
* @type {external:MediaRecorder}
*/
mediaRecorder: null,
/**
* Array of data chunks collected during the recording
* @name module:media/AudioBuffer.AudioBuffer#chunks
* @type {external:Blob[]}
*/
chunks: null,
/**
* The HTML audio element used to play the recorded sound
* @name module:media/AudioBuffer.AudioBuffer#mediaPlayer
* @type {external:HTMLAudioElement}
*/
mediaPlayer: null,
/**
* The identifier of the timer launched to stop the recording when the maximum time is exceeded.
* This member is `null` when no timeout function is associated to this AudioBuffer
* @name module:media/AudioBuffer.AudioBuffer#timeoutID
* @type {number}
*/
timeoutID: null,
/**
* Instructs this AudioBuffer recorder to start playing the collected audio at the end of the
* current `mediaRecorder` task.
* @name module:media/AudioBuffer.AudioBuffer#playWhenFinished
* @type {boolean}
*/
playWhenFinished: false,
});
/**
* Maximum amount of time allowed for recordings (in seconds)
* @type {number}
*/
AudioBuffer.MAX_RECORD_LENGTH = 180;
export default AudioBuffer;