UNPKG

transcord

Version:

A simple recording and transcription module.

475 lines (398 loc) 23.4 kB
(function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as anonymous module. define(['jquery'], factory); } else if (typeof exports === 'object') { // Node / CommonJS factory(require('jquery')); } else { // Browser globals. factory(jQuery); } })(function ($) { 'use strict'; var AudioContext = window.AudioContext || window.webkitAudioContext; if (!navigator.getUserMedia){ navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; } // Constants var NAMESPACE = 'transcord'; // Events var EVENT_INIT = 'init.' + NAMESPACE; var EVENT_START = 'start.' + NAMESPACE; var EVENT_STOP = 'stop.' + NAMESPACE; var EVENT_BLOB = 'blob.' + NAMESPACE; function toArray(obj, offset) { var args = []; args.push(offset); return args.slice.apply(obj, args); } function Transcord(element, options) { this.$element = $(element); this.options = $.extend({}, Transcord.DEFAULTS, $.isPlainObject(options) && options); this.recognizing = false; this.stream = null; this.speechRecognitionIsSupported = false; this.context = null; this.recordingLength = 0; this.leftchannel = []; this.rightchannel = []; this.sampleRate = 2048; this.transcriptBuffer = ''; this.blobsURL = []; this.blobs = []; this.init(); } Transcord.prototype = { constructor : Transcord, init: function () { // Bind events; this.bind(); this.$element.trigger(EVENT_INIT); // Ask for user media. var _this = this; if (this.options.record && navigator.getUserMedia){ navigator.getUserMedia({ audio:true }, function (stream) { _this.stream = stream; }, function () { console.log ('No audio device detected'); return; }); }else { console.error('getUserMedia not available'); return; } // Display the mic Icon. if ($.type(this.options.showMicIconNode) === 'boolean' && this.options.showMicIconNode){ this.displayMic(this.$element.parent()); }else if (this.options.showMicIconNode instanceof jQuery){ this.displayMic(this.options.showMicIconNode); } if (this.options.transcript && 'webkitSpeechRecognition' in window) { this.speechRecognitionIsSupported = true; this.recognition = new webkitSpeechRecognition(); // That is the object that will manage our whole recognition process. this.recognition.continuous = true; // Suitable for dictation. this.recognition.interimResults = this.options.transcriptInterimResults; // If we want to start receiving results even if they are not final. this.recognition.lang = this.options.transcriptLanguage; this.recognition.maxAlternatives = 1; // Since from our experience, the highest result is really the best... if (this.options.grammar){ this.recognition.grammar = this.options.transcriptgrammar; } // Results of the recognition this.recognition.onresult = function (event) { // Check if everything is OK, quit if not. if (typeof(event.results) === 'undefined') { _this.recognition.stop(); console.error('Results are undefined, stopping recognition'); return; } for (var i = event.resultIndex; i < event.results.length; ++i) { if (event.results[i].isFinal) { // Final results if (_this.options.isTranscriptPersistent){ _this.transcriptBuffer+= event.results[i][0].transcript; } _this.$element.text(_this.transcriptBuffer || event.results[i][0].transcript); } else { // Not final _this.$element.text(_this.transcriptBuffer + event.results[i][0].transcript) ; } } }; } else { console.log('Speech recognition is not supported by this navigator'); } }, startRecording : function () { this.$element.trigger(EVENT_START); this.$micImg.trigger(EVENT_START); if (this.options.transcript && this.speechRecognitionIsSupported){ this.recognition.start(); } if (!this.options.record) { return; } // creates the audio context this.context = new AudioContext(); // retrieve the current sample rate to be used for WAV packaging this.sampleRate = this.context.sampleRate; // creates a gain node let volume = this.context.createGain(); // creates an audio node from the microphone incoming stream let audioInput = this.context.createMediaStreamSource(this.stream); // connect the stream to the gain node audioInput.connect(volume); var bufferSize = 2048; let recorder = this.context.createScriptProcessor(bufferSize, 2, 2); var _this = this; this.recognizing = true; recorder.onaudioprocess = function (e) { if (!_this.recognizing){ return; } var left = e.inputBuffer.getChannelData (0); var right = e.inputBuffer.getChannelData (1); // we clone the samples _this.leftchannel.push (new Float32Array (left)); _this.rightchannel.push (new Float32Array (right)); _this.recordingLength += this.bufferSize; }; // we connect the recorder volume.connect (recorder); recorder.connect (this.context.destination); }, stopRecording : function () { this.$element.trigger(EVENT_STOP); this.$micImg.trigger(EVENT_STOP); this.recognizing = false; if (this.options.transcript && this.speechRecognitionIsSupported){ this.recognition.stop(); } if (this.options.record) { let blob = this.writeWaw(); this.blobs.push(blob); this.$element.trigger(EVENT_BLOB, [blob]); this.context.close(); return blob; } return null; }, writeWaw : function () { function mergeBuffers(channelBuffer, recordingLength) { var result = new Float32Array(recordingLength); var offset = 0; var lng = channelBuffer.length; for (var i = 0; i < lng; i++){ let buffer = channelBuffer[i]; result.set(buffer, offset); offset += buffer.length; } return result; } function interleave(leftChannel, rightChannel) { var length = leftChannel.length + rightChannel.length; var result = new Float32Array(length); var inputIndex = 0; for (var index = 0; index < length;){ result[index++] = leftChannel[inputIndex]; result[index++] = rightChannel[inputIndex]; inputIndex++; } return result; } function writeUTFBytes(view, offset, string) { var lng = string.length; for (var i = 0; i < lng; i++){ view.setUint8(offset + i, string.charCodeAt(i)); } } // we flat the left and right channels down var leftBuffer = mergeBuffers (this.leftchannel, this.recordingLength); var rightBuffer = mergeBuffers (this.rightchannel, this.recordingLength); // we interleave both channels together var interleaved = interleave (leftBuffer, rightBuffer); // create the buffer and view to create the .WAV file var buffer = new ArrayBuffer(44 + interleaved.length * 2); var view = new DataView(buffer); // write the WAV container, check spec at: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ // RIFF chunk descriptor writeUTFBytes(view, 0, 'RIFF'); view.setUint32(4, 44 + interleaved.length * 2, true); writeUTFBytes(view, 8, 'WAVE'); // FMT sub-chunk writeUTFBytes(view, 12, 'fmt '); view.setUint32(16, 16, true); view.setUint16(20, 1, true); // stereo (2 channels) view.setUint16(22, 2, true); view.setUint32(24, this.sampleRate, true); view.setUint32(28, this.sampleRate * 4, true); view.setUint16(32, 4, true); view.setUint16(34, 16, true); // data sub-chunk writeUTFBytes(view, 36, 'data'); view.setUint32(40, interleaved.length * 2, true); // write the PCM samples var lng = interleaved.length; var index = 44; var volume = 1; for (var i = 0; i < lng; i++){ view.setInt16(index, interleaved[i] * (0x7FFF * volume), true); index += 2; } // our final binary blob that we can hand off var blob = new Blob ([view], { type : 'audio/wav' }); this.leftchannel.length = 0; this.rightchannel.length = 0; this.recordingLength = 0; return blob; }, toogleRecord : function () { if (this.recognizing){ return this.stopRecording(); }else { this.startRecording(); } }, displayBlob : function (blob,$node) { if (!blob || !(blob instanceof Blob)){ return; } // If the number of blob will be over the number of blob allowed, destroy the first recorded Blob. if (this.blobsURL.length === this.options.recordNumber){ this.destroyBlob(0); } let url = window.URL.createObjectURL(blob); this.blobsURL.push(url); // The audio tag will be displayed at the chosen node, let $nodeToAppend = $node || this.$element.parent(); $nodeToAppend.append('<audio controls src="' + url + '"></audio>'); }, destroyBlob : function (index) { var _this = this; function destroy (url) { // Remove object with animation $('audio[src="' + url + '"]').hide('slow', function () { $('audio[src="' + url + '"]').remove(); }); window.URL.revokeObjectURL(url); let index = _this.blobsURL.indexOf(url); if (index !== -1){ _this.blobsURL.splice(index, 1); _this.blobs.splice(index, 1); } } if (typeof index === 'string' && parseInt(index, 10) !== 'number'){ var corresponding = { first : 0, last : (this.blobsURL.length - 1), all : Object.keys(this.blobsURL) }; if (!corresponding[index]){ console.error('Invalid index : "' + index + '"'); return; }else { index = corresponding[index]; } } if (index.constructor === Array){ let length = index.length; var values = []; for (let i = 0; i < length && !isNaN(parseInt(index[i], 10)); i++){ values.push(this.blobsURL[i]); } length = values.length; for (let i = 0; i < length; i++){ destroy(values[i]); } }else if (typeof parseInt(index, 10) === 'number'){ destroy(this.blobsURL[index]); } }, getBlobs : function () { // Returns copy of blobs array return this.blobs.slice(); }, trigger: function (type, data) { var e = $.Event(type, data); this.$element.trigger(e); return e; }, displayMic : function ($node) { let _this = this; this.$micImg = $('<img class=\'transcord_mic\' src=\'' + this.options.imgMicIdle + '\'>') .on(EVENT_START, function () { $(this).attr('src', _this.options.imgMicRecording); }) .on(EVENT_STOP, function () { $(this).attr('src', _this.options.imgMicIdle); }) .on('click', function () { _this.toogleRecord(); }) .appendTo($node); }, downloadBlob : function (blob, filename) { let url = window.URL.createObjectURL(blob); // Simulate a link. let $mah = $('<a/>') .hide() .attr('href', url) .attr('download', (filename || 'new record')) .appendTo($('body')); // Click on link $mah[0].click(); // Clear things up. window.URL.revokeObjectURL(url); $mah.remove(); }, bind : function () { let $el = this.$element; if ($.isFunction(this.options.onInit)) { $el.on(EVENT_INIT, this.options.onInit); } if ($.isFunction(this.options.onStart)) { $el.on(EVENT_START, this.options.onStart); } if ($.isFunction(this.options.onStop)) { $el.on(EVENT_STOP, this.options.onStop); } if ($.isFunction(this.options.onStop)) { $el.on(EVENT_BLOB, this.options.onBlob); } } }; Transcord.DEFAULTS = { // Record audio record : true, // Transcript speech to text transcript : true, // Transcription language transcriptLanguage : 'en-US', // Transcription returns interim results transcriptInterimResults : true, // Define if the old text must be kept when updating transcription isTranscriptPersistent : true, // Set of words that can be recognized transcriptgrammar : undefined, // Things to do on initialization onInit : null, // Things to do when record/transcript starts onStart : null, // Things to do when record/transcript stops onStop : null, // Things to do when a record is ready onBlob : null, // Number of record to be displayed at the same time. recordNumber : 1, // Show mic icon with events binded. showMicIconNode : true, // Idle mic img. imgMicIdle : ' data:image/gif;base64,R0lGODdhMgAyAPUAAJqampubm5ycnJ2dnZ6enp+fn6CgoKSkpKWlpaampqurq62tra+vr7KysrS0tLW1tbe3t7i4uLm5ucTExMXFxcbGxsfHx8zMzM3Nzc7Ozs/Pz9jY2NnZ2dzc3N3d3d7e3t/f3+Pj4+bm5ujo6Orq6uzs7PLy8vPz8/T09PX19fb29vf39/r6+vv7+/z8/P39/f7+/v///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUALAAAAAAyADIAAAa0wJhwSCwaj8ikcslsOp/QqHRKrVqv2Kx2y+16v+CweExGjkaustkAaHPUxVB7DgjBhw16u3EXFvRtfTGAgX2EAYKHiYSLgI16j3RlHyBDAYxCIB8pXiKFMQqAC5Z1X20kQhmAGpmfXQsAE3h0EjCpAKNfHKdDIBUVla2lYAyucQe4tmEJbi1FFgLGxHMQFNYEeypqGGyrynAeEnsdLILCm+bn6errH83r8PHy8/T19vf4+VZBADs=', // Recording mic img imgMicRecording : 'data:image/gif;base64,R0lGODlhMgAyAPcJAPwERP6mvP/o7v/l7P/m7P/+/vwfWPwPTP/4+vwvZPw8bv1Nev1Wgf2AoP6Srv6ctf6guP6huf6lvP/19/wzZ/6xxf65y//C0v////sDQ/wIR//6+/1ljPwlXfw/cP1Xgf6btP67zfw0aPw6bP/r8PwgWfw7bf/v8/1FdP1Ec/wHRv6twv/h6f6Trv6Ur/62yf1hiv13mvwFRf1uk/wST/1dh/6uw/12mf6Nqv6QrP/c5f/q7/1Yg/6WsfwGRv1QfPwVUf6swv69zv/G1f1zl//W4f16nP/7/PwTT//a4/6ft/6XsfwMSvwZVP63yv66zP/Q3P/09/wsYv/2+P1Rff6et9s2YN7e3txOc/1Dc/6KqP/L2P/M2f/Y4v/u8v1GdfxCcvwOTP1skvwoX9nZ2aCgoP1chubEzf/S3vTx8v/8/dnCx826v6WUmK6Vm66VnLOUnLSUnLmrr/2Bocy5vqSUmJ+XmZ+XmqyPl9za2rGMlvS1xf64ytjBxt3b26yQl/b29vf39+Pj4/Pz8/7+/vr6+vLy8ujo6Pz8/P/x9PwuY5+VmP/5+vwkXPw+b9xdfv1Ledd6k/1oj/61yKWlpaampq+vr7KysrS0tLm5ubW1tczMzPT09PX19djY2Nzc3N3d3a2trebm5s3NzZ2Vl56Zmt2tuv/B0fwXUv/T3vwaVJ+SlfgJR+QmV9Y7Y56Ulp2UlvoFRKx/i9wzXv1ylp2YmeggVOYkVpuampqamvoGRf1tktw7ZOghVLZ6ipuZmrdtgMJeeP/V4KORlrZtgKqBjJ+dnZqZmfkGRctNbsxLbMVYdPwtYsNgeZyXmPcKR8RYdKx+iq57iLNzhLJ0hLF2hctPb8pQb/ATTPETTJ+TluMpWeAtW6WKkdk3Ydo3YdU9ZPYKR/MPSu4WTu8WTvoERNY9ZaSLka58ibdsgPIQSv1Jd/wJSP6ovvwWUv/k6/1ji/6yxvw9bv/w9PwmXf1vlP1Idv6Ipv15m6OLkbVugZuYmcJdd8NbdsVXc5uZmSH/C05FVFNDQVBFMi4wAwEAAAAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNrDWdhbW1hPTAuNDU0NTUAIf8LSW1hZ2VNYWdpY2sNZ2FtbWE9MC40NTQ1NQAh/wtJbWFnZU1hZ2ljaw1nYW1tYT0wLjQ1NDU1ACH/C0ltYWdlTWFnaWNr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' }; Transcord.setDefaults = function (options) { $.extend(Transcord.DEFAULTS, options); }; // Register as jQuery plugin $.fn.transcord = function (option) { var args = toArray(arguments, 1); var result; this.each(function () { var $this = $(this); var data = $this.data(NAMESPACE); var options; var fn; if (!data) { if (/destroy/.test(option)) { return; } options = $.extend({}, $this.data(), $.isPlainObject(option) && option); $this.data(NAMESPACE, (data = new Transcord(this, options))); } if (typeof option === 'string' && $.isFunction(fn = data[option])) { result = fn.apply(data, args); } }); return typeof result === 'undefined' ? this : result; }; $.fn.transcord.Constructor = Transcord; $.fn.transcord.setDefaults = Transcord.setDefaults; });