const path = require('path'); module.exports = function(RED) { function HTML(config) { var configAsJson = JSON.stringify(config); var html = String.raw``; if (config.press && config.press === "press"){ html += String.raw``; } else { html += String.raw``; } return html; } function checkConfig(node, conf) { if (!conf || !conf.hasOwnProperty("group")) { node.error(RED._("ui_microphone.error.no-group")); return false; } return true; } var ui = undefined; // instantiate a ui variable to link to the dashboard function MicrophoneNode(config) { try { var node = this; if(ui === undefined) { ui = RED.require("node-red-dashboard")(RED); } RED.nodes.createNode(this, config); // placing a "debugger;" in the code will cause the code to pause its execution in the web browser // this allows the user to inspect the variable values and see how the code is executing //debugger; var done = null; if (checkConfig(node, config)) { var html = HTML(config); // *REQUIRED* get the HTML for this node using the function from above done = ui.addWidget({ // *REQUIRED* add our widget to the ui dashboard using the following configuration node: node, // *REQUIRED* order: config.order, // *REQUIRED* placeholder for position in page group: config.group, // *REQUIRED* width: config.width, // *REQUIRED* height: config.height, // *REQUIRED* format: html, // *REQUIRED* templateScope: "local", // *REQUIRED* emitOnlyNewValues: false, // *REQUIRED* forwardInputMessages: false, // *REQUIRED* storeFrontEndInputAsState: false, // *REQUIRED* convertBack: function (value) { return value; }, beforeEmit: function(msg) { return { msg: msg }; }, beforeSend: function (msg, orig) { if (orig) { return orig.msg; } }, /** * The initController is where most of the magic happens. * This is the section where you will write the Javascript needed for your node to function. * The 'msg' object will be available here. */ initController: function($scope) { var isAudio = true; $scope.init = function (config) { //console.log("ui_microphone: initialised config:",config); $scope.config = config; var fac = "fa-2x"; // default if (parseInt(config.width || 0) !== 1) { // don't scale it if it;s only 1 wide fac = parseInt(config.height || 0) + 1; if (fac < 2) { fac = 2; } // 2 as the minimum... unless 1x1 if (fac > 5) { fac = 5; } // Can't be larger than 5x fac = "fa-"+fac+"x"; } else if (parseInt(config.width || 0) === 1) { fac = "fa-lg"; } // shrink if it's 1x1 setTimeout(function() { $("#microphone_control_"+$scope.$id+" i").addClass(fac); }, 0); isAudio = (config.mode !== "recog"); if (isAudio) { $scope.enabled = !!navigator.mediaDevices; } else { $scope.enabled = false; try { $scope.enabled = (!!webkitSpeechRecognition || !!SpeechRecognition); } catch (e) { } } if (!$scope.enabled) { setTimeout(function() { $("#microphone_control_"+$scope.$id+" i").removeClass("fa-microphone").addClass("fa-microphone-slash"); },50); } }; var worker; var mediaRecorder; var audioContext; var speechRecognition; var stopTimeout; var active = false; //////////???????????????????????////////////// MAIN CODE $scope.on('input', function(msg, send, done) { $scope.toggleMicrophone(true); node.status({ fill: "green", shape: "dot", text: "FELVÉTEL" }); if (done) { done(); } }); //////////???????????????????????////////////// MAIN CODE //var button = $("#microphone_control_"+$scope.$id); $scope.toggleMicrophone = function(e) { if (e === true) { active = false; } if (!$scope.enabled) return; if (!active) { active = true; $("#microphone_control_"+$scope.$id+" i").removeClass("fa-microphone").addClass("fa-rotate-right fa-spin"); if (isAudio) { navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(handleSuccess).catch(handleError); } else { try { handleSuccess(); } catch (e) { handleError(e); } } } else { if (isAudio) { if (mediaRecorder) { mediaRecorder.stop(); } } else { if (speechRecognition) { speechRecognition.stop(); active = false; $("#microphone_control_"+$scope.$id+" i").addClass("fa-microphone").removeClass("fa-rotate-right fa-spin"); } } } }; $scope.stop = function() { if (active) { if (isAudio) { mediaRecorder.stop(); } else { if (speechRecognition) { speechRecognition.stop(); active = false; $("#microphone_control_"+$scope.$id+" i").addClass("fa-microphone").removeClass("fa-rotate-right fa-spin"); } } } }; var handleError = function(err) { console.warn("Failed to access microphone:",err); active = false; $("#microphone_control_"+$scope.$id+" i").addClass("fa-microphone").removeClass("fa-rotate-right fa-spin"); }; var handleSuccess = function(stream) { if (isAudio) { mediaRecorder = new MediaRecorder(stream, {mimeType: 'audio/webm'}); mediaRecorder.ondataavailable = function(evt) { if (evt.data.size > 0) { sendBlob(new Blob([evt.data])); } }; mediaRecorder.onstop = function() { if (active) { active = false; $("#microphone_control_"+$scope.$id+" i").addClass("fa-microphone").removeClass("fa-rotate-right fa-spin"); if (stopTimeout) { clearTimeout(stopTimeout); stopTimeout = null; } stream.getTracks().forEach( function(track) { track.stop() }); } }; // Timeslice is not current exposed. // var timeslice = 0; // if ($scope.config.timeslice) { // timeslice = parseInt($scope.config.timeslice)*1000; // } // if (timeslice) { // mediaRecorder.start(timeslice); // } else { mediaRecorder.start(); // } if ($scope.config && $scope.config.maxLength && ($scope.config.press !== "press")) { stopTimeout = setTimeout(function() { if (active) { mediaRecorder.stop(); } }, $scope.config.maxLength*1000) } else if (!$scope.config) { console.warn("Microphone node not initialised with user configuration. Using defaults"); } } else { // !isAudio var sr = webkitSpeechRecognition || SpeechRecognition; var interim = ($scope.config && $scope.config.interimResults); speechRecognition = new sr(); speechRecognition.continuous = true; speechRecognition.interimResults = interim; speechRecognition.maxAlternatives = 1; speechRecognition.onresult = function (e) { var res = e.results; var len = res.length; if (len > 0) { var r = res[len -1]; var text = r[0].transcript; var msg = {payload: text}; if (interim) { msg.done = r.isFinal; } else { msg.done = true; } $scope.send(msg); } }; speechRecognition.onend = function () { if (active) { active = false; $("#microphone_control_"+$scope.$id+" i").addClass("fa-microphone").removeClass("fa-rotate-right fa-spin"); } }; speechRecognition.onerror = function (e) { handleError(e); }; speechRecognition.start(); if ($scope.config && ($scope.config.maxRecogLength > 0) && ($scope.config.press !== "press")) { stopTimeout = setTimeout(function() { if (active) { speechRecognition.stop(); } }, $scope.config.maxRecogLength*1000) } else if (!$scope.config) { console.warn("Microphone node not initialised with user configuration. Using defaults"); } } }; var sendBlob = function(blob) { if (!audioContext) { audioContext= new AudioContext() } var fileReader = new FileReader() // Set up file reader on loaded end event fileReader.onloadend = function() { audioContext.decodeAudioData(fileReader.result, function(audioBuffer) { convertToWav(audioBuffer) }) } fileReader.readAsArrayBuffer(blob) }; var convertToWav = function(buffer) { if (!worker) { worker = new Worker('ui_microphone/recorderWorker.js'); } worker.postMessage({ command: 'init', config: {sampleRate: 44100} }); worker.onmessage = function( e ) { $scope.send({payload:e.data}); worker.postMessage({ command: 'clear' }); }; worker.postMessage({ command: 'record', buffer: [ buffer.getChannelData(0), buffer.getChannelData(0) ] }); worker.postMessage({ command: 'exportMonoWAV', type: 'audio/wav' }); }; } }); } } catch (e) { // eslint-disable-next-line no-console console.warn(e); // catch any errors that may occur and display them in the web browsers console } node.on("close", function() { if (done) { done(); } }); } /** * REQUIRED * Registers the node with a name, and a configuration. * Type MUST start with ui_ */ RED.nodes.registerType("ui_microphone", MicrophoneNode); var uipath = 'ui'; if (RED.settings.ui) { uipath = RED.settings.ui.path; } var fullPath = path.join('/', uipath, '/ui_microphone/*').replace(/\\/g, '/'); RED.httpNode.get(fullPath, function (req, res) { var options = { root: __dirname + '/lib/', dotfiles: 'deny' }; res.sendFile(req.params[0], options) }); };