Initial commit

This commit is contained in:
Marcelo
2025-11-20 15:27:34 -06:00
commit cc72c9fc5d
3221 changed files with 737477 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
# CONTRIBUTING
## Code
* The code should follow Watson Developer Cloud [coding guidances](https://github.com/watson-developer-cloud/api-guidelines)
* The code should follow: https://github.com/airbnb/javascript
* 2 spaces identation
* `snake_case`
## Issues
If you encounter an issue with using the labs here, you are welcome to submit
a [bug report](https://github.com/node-red-contrib-utils/node-red-contrib-media-utils/issues).
Before that, please search for similar issues. It's possible somebody has already encountered this issue.
## Pull Requests
If you want to contribute to the repository, follow these steps:
1. Fork the repo.
2. Develop code changes.
5. Commit your changes.
6. Push to your fork and submit a pull request.
### Getting started
* Install [Node-RED](http://nodered.org/)
* Fork [this repo](https://github.com/node-red-contrib-utils/node-red-contrib-media-utils)
* Clone the project
* Create an npm link to your forked project
* Within the cloned directory, run `npm link`
* Within the Node-RED directory, run `npm link node-red-contrib-media-utils`
* Run Node-RED
Now you should have the media nodes installed in your local palette, allowing you to work on your project locally.
### Modify or Create new nodes
If you want to add a node, create a folder using the same naming convention.
Please refer to the [Node-RED](http://nodered.org/docs/creating-nodes/) documentation.

37
node_modules/node-red-contrib-browser-utils/README.md generated vendored Normal file
View File

@@ -0,0 +1,37 @@
# Node-RED Contrib Browser Utils
[Node-RED](http://nodered.org) nodes for browser functionality such as file upload, camera & microphone.
## Install
Run the following command in the root directory of your Node-RED install:
npm install node-red-contrib-browser-utils
## Usage
### Camera
The browser takes a picture with the default camera when the button next to the node is clicked. The node outputs it as a PNG buffer.
The `camera` node has a 2000ms delay to prevent slow camera driver startup causing an issue in some browsers.
### Microphone
The browser starts recording after the button next to the node is clicked and stops it when the button is clicked again. The node outputs it as a WAV buffer.
### File upload
The node accepts a file to be uploaded and outputs it as a buffer.
## Contributing
For simple typos and fixes please just raise an issue pointing out our mistakes. If you need to raise a pull request please read our [contribution guidelines](https://github.com/node-red-contrib-utils/node-red-contrib-browser-utils/blob/master/CONTRIBUTING.md) before doing so.
## Copyright and license
Copyright 2014, 2016, 2019 IBM Corp. under the Apache 2.0 license.

View File

@@ -0,0 +1,152 @@
<!--
Copyright 2013, 2016, 2019 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="camera">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Camera">
</div>
</script>
<script type="text/x-red" data-help-name="camera">
<p>A simple camera node to capture the current image from the device webcam</p>
<p>Usage:</p>
<ol>
<li>Add the camera node to your flow</li>
<li>Click the <code>button</code> capture an image from the webcam</li>
</ol>
<p>The <code>png</code> image is sent as the <code>msg.payload</code> object</p>
<p>Supported browsers</p>
<ul>
<li>Chrome</li>
<li>Firefox</li>
</ul>
</script>
<script type="text/javascript">
(function() {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
window.video = document.createElement('video')
window.canvas = document.createElement('canvas')
canvas.style.display = 'none'
canvas.style.display = 'none'
var id
function onCanPlay(evt) {
video.removeEventListener('canplay', onCanPlay, false)
var width = 320
var height = video.videoHeight / (video.videoWidth / width)
if (isNaN(height)) {
height = width / (4 / 3)
}
setTimeout(function(evt) {
video.setAttribute('width', width)
video.setAttribute('height', height)
canvas.setAttribute('width', width)
canvas.setAttribute('height', height)
var ctx = canvas.getContext('2d')
ctx.drawImage(video, 0, 0, width, height)
canvas.toBlob(function(blob) {
uploadRecord(id, blob)
})
}, 2000)
}
function onStreamReady(id, stream) {
// As of Chrome 71 createObjectURL is dropped so try direct
// access first
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject
try {
video.srcObject = stream;
} catch (error) {
video.src = window.URL.createObjectURL(stream)
}
video.addEventListener('canplay', onCanPlay, false)
video.play()
}
function onVideoPlaying(id, evt) {
canvas.style.height = video.clientHeight + 'px'
canvas.style.width = video.clientWidth + 'px'
var ctx = canvas.getContext('2d')
ctx.drawImage(video, 0, 0)
canvas.toBlob(function(blob) {
uploadRecord(id, blob)
})
}
function setCameraStatus(record, id) {
$.getJSON('node-red-camera/status', {status: record, id: id})
.done(function () {})
.fail(function (err) {
console.log(err);
})
.always(function () {});
}
function takeSnapshot(id) {
const constraints = {video : true, audio : false};
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
setCameraStatus(true, id)
onStreamReady(id, stream)
})
.catch(function(err) {
window.alert('Your browser does not support the camera node')
console.log(err)
})
}
function uploadRecord(id, blob) {
var xhr = new XMLHttpRequest()
xhr.open('POST', 'node-red-camera/' + id, true)
xhr.send(blob)
}
RED.nodes.registerType('camera', {
category: 'input',
defaults: {
name: {value: ''}
},
color: 'rgb(215, 201, 194)',
inputs: 0,
outputs: 1,
icon: 'camera.png',
paletteLabel: 'camera',
label: function() {
return this.name || 'camera';
},
labelStyle: function() {
return this.name ? 'node_label_italic' : '';
},
button: {
onclick: function(){
id = this.id
takeSnapshot(id)
}
}
});
})();
</script>

View File

@@ -0,0 +1,66 @@
/**
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var bodyParser = require('body-parser')
module.exports = function (RED) {
function Node (config) {
RED.nodes.createNode(this, config);
var node = this;
var requestSize = '50mb';
RED.httpAdmin.get('/node-red-camera/status', function (req, res) {
var n = RED.nodes.getNode(req.query.id)
var status = {};
if ('true' == req.query.status) {
status = {fill:'red', shape:'dot', text:'taking picture...'}
}
if (n) {
n.status(status);
}
res.json({});
});
RED.httpAdmin.post('/node-red-camera/:id', bodyParser.raw({ type: '*/*', limit: requestSize }), function(req,res) {
var node = RED.nodes.getNode(req.params.id)
if (node != null) {
try {
node.receive({payload: req.body})
node.status({})
res.sendStatus(200)
} catch(err) {
node.status({fill:'red', shape:'dot', text:'upload failed'});
res.sendStatus(500)
node.error(RED._("upload-camera.failed", { error: err.toString() }))
}
} else {
res.status(404).send("no node found")
}
})
this.on('input', function (msg) {
if(msg.payload !== '') {
node.send(msg)
}
})
}
RED.nodes.registerType('camera', Node)
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,158 @@
<!--
Copyright 2013, 2016, 2018, 2021 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="fileinject">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="File Inject">
</div>
</script>
<script type="text/x-red" data-help-name="fileinject">
<p>Simple file inject node</p>
<p>Press the button to inject a file directly to the flow.</p>
<p>File will be sent as part of the <code>msg.payload</code> object.</p>
</script>
<script type="text/javascript">
(function() {
var id
var inputElement = document.createElement('input')
inputElement.setAttribute('type', 'file')
inputElement.addEventListener('change', injectFile)
document.body.appendChild(inputElement)
function simulate(element, eventName){
var options = extend(defaultOptions, arguments[2] || {});
var oEvent, eventType = null;
for (var name in eventMatchers){
if (eventMatchers[name].test(eventName)) { eventType = name; break; }
}
if (!eventType)
throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');
if (document.createEvent){
oEvent = document.createEvent(eventType);
if (eventType == 'HTMLEvents'){
oEvent.initEvent(eventName, options.bubbles, options.cancelable);
}
else{
oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView,
options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
}
element.dispatchEvent(oEvent);
}
else{
options.clientX = options.pointerX;
options.clientY = options.pointerY;
var evt = document.createEventObject();
oEvent = extend(evt, options);
element.fireEvent('on' + eventName, oEvent);
}
return element;
}
function extend(destination, source) {
for (var property in source)
destination[property] = source[property];
return destination;
}
var eventMatchers = {
'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
'MouseEvents': /^(?:click|dblclick|mouse(?:down|up|over|move|out))$/
}
var defaultOptions = {
pointerX: 0,
pointerY: 0,
button: 0,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
bubbles: true,
cancelable: true
}
function triggerClick(event) {
simulate(inputElement, 'click')
}
function setNodeStatus(uploading, id) {
$.getJSON('node-red-fileinject/status', {status: uploading, id: id})
.done(function () {})
.fail(function (err) {
console.log(err);
})
.always(function () {});
}
function injectFile() {
var blob;
var currentInput = this
if(currentInput.files.length !== 0) {
var file = currentInput.files[0]
var reader = new FileReader()
setNodeStatus(true, id)
reader.addEventListener('load', function(onLoadEvent) {
var filename = file.name
blob = new Blob([onLoadEvent.target.result], {
type: (file.type=='')?'application/octet-stream':file.type
});
currentInput.value = ''
var xhr = new XMLHttpRequest()
xhr.open('POST', 'node-red-fileinject/' + id, true)
xhr.setRequestHeader('x-filename', filename);
xhr.send(blob)
})
reader.readAsArrayBuffer(file)
} else {
return
}
}
RED.nodes.registerType('fileinject', {
category: 'input',
defaults: {
name: {value: ''}
},
color: 'rgb(254, 245, 136)',
inputs: 0,
outputs: 1,
icon: 'fileinject.png',
paletteLabel: 'file inject',
label: function() {
return this.name || 'file inject';
},
labelStyle: function() {
return this.name ? 'node_label_italic' : '';
},
button: {
onclick: function(evt){
id = this.id
triggerClick(evt)
}
}
});
})();
</script>

View File

@@ -0,0 +1,64 @@
/**
* Copyright 2013, 2016, 2018, 2021 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var bodyParser = require('body-parser')
module.exports = function (RED) {
var requestSize = '50mb'
function Node (config) {
RED.nodes.createNode(this, config);
var node = this;
RED.httpAdmin.get('/node-red-fileinject/status', function (req, res) {
var n = RED.nodes.getNode(req.query.id)
var status = {};
if ('true' == req.query.status) {
status = {fill:'red', shape:'dot', text:'sending file...'}
}
if (n) {
n.status(status);
}
res.json({});
});
RED.httpAdmin.post('/node-red-fileinject/:id', bodyParser.raw({ type: '*/*', limit: requestSize }), function(req,res) {
var node = RED.nodes.getNode(req.params.id)
if (node != null) {
try {
node.receive({payload: req.body, filename: req.headers["x-filename"], mimetype: req.headers["content-type"]})
node.status({})
res.sendStatus(200)
} catch(err) {
res.sendStatus(500)
node.error(RED._("inject-file.failed", { error: err.toString() }))
}
} else {
res.status(404).send("no node found")
}
})
this.on('input', function (msg) {
if(msg.payload !== '') {
node.send(msg)
}
})
}
RED.nodes.registerType('fileinject', Node)
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,176 @@
<!--
Copyright 2013, 2016, 2018, 2019 IBM Corp.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<script type="text/x-red" data-template-name="microphone">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Microphone">
</div>
</script>
<script type="text/x-red" data-help-name="microphone">
<p>A simple microphone node to record audio directly from the web browser</p>
<p>Usage:</p>
<ol>
<li>Note - you must be using <code>https</code> to record</li>
<li>Click the <code>button</code> to start recording</li>
<li>Click the <code>button</code> again to stop recording</li>
</ol>
<p>The recorded audio is stored in the nodes <code>msg.payload</code> object</p>
<p>To see if the microphone node is supported in your browser check <a href="http://caniuse.com/#feat=stream" target="_blank">this</a> page</p>
<p>Supported output formats:</p>
<ul>
<li>WAV</li>
</ul>
</script>
<script type="text/javascript">
(function() {
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
window.AudioContext = window.AudioContext || window.webkitAudioContext || window.mozAudioContext || window.oAudioContext || window.msAudioContext;;
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL;
const MICSTATUS = {
OFF : '1',
ON : '2',
CONTEXTERROR : '4'
}
$.getScript( 'microphone/js/recorder.js' )
.done(function( script, textStatus ) {
// script loaded, do nothing
})
.fail(function( jqxhr, settings, exception ) {
console.log('FAILED to load recorder script: '+exception)
});
var instances = {}
var audio_context = new AudioContext
function checkAudioContextState() {
var p = new Promise(function resolver(resolve, reject) {
if (audio_context.state && 'running' !== audio_context.state && audio_context.resume) {
audio_context.resume().then(() => {
resolve()
})
} else {
resolve()
}
});
return p
}
function startRecording(id, stream) {
checkAudioContextState()
.then(() => {
var input = audio_context.createMediaStreamSource(stream)
instances[id] = new Recorder(input)
instances[id] && instances[id].record()
})
.catch(() => {
console.log('Audio Context Error')
setMicStatus(MICSTATUS.CONTEXTERROR, id)
})
}
function stopRecording(id, callback) {
if(!instances[id]) {
return
}
instances[id].stop()
instances[id].exportWAV(function(blob) {
instances[id].clear()
instances[id] = null
callback & callback(id, blob)
})
setMicStatus(MICSTATUS.OFF, id);
}
function getRecorder(id) {
const constraints = {video : false, audio : true};
navigator.mediaDevices.getUserMedia(constraints)
.then(function(stream) {
setMicStatus(MICSTATUS.ON, id)
startRecording(id, stream)
})
.catch(function(err) {
window.alert('Your browser does not support audio recording')
console.log(err)
})
}
function getRecorderXX(id) {
if(!navigator.getUserMedia) {
window.alert('Your browser does not support audio recording')
return
}
navigator.getUserMedia({audio: true},
function(stream){
setMicStatus(MICSTATUS.ON, id)
startRecording(id, stream)
},
function(e) {
window.alert(e.message)
})
}
function uploadRecord(id, blob) {
var xhr = new XMLHttpRequest()
xhr.open('POST', 'node-red-microphone/' + id, true)
xhr.send(blob)
}
function setMicStatus(record, id) {
$.getJSON('node-red-microphone/status', {status: record, id: id})
.done(function () {})
.fail(function (err) {
console.log(err);
})
.always(function () {});
}
RED.nodes.registerType('microphone', {
category: 'input',
defaults: {
name: {value: ''}
},
color: 'rgb(204, 230, 171)',
inputs: 0,
outputs: 1,
icon: 'microphone.png',
paletteLabel: 'microphone',
label: function() {
return this.name || 'microphone';
},
labelStyle: function() {
return this.name ? 'node_label_italic' : '';
},
button: {
onclick: function(){
if(instances[this.id]) {
stopRecording(this.id, uploadRecord)
} else {
getRecorder(this.id)
}
}
}
});
})();
</script>

View File

@@ -0,0 +1,84 @@
/**
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the 'License');
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var bodyParser = require('body-parser')
module.exports = function (RED) {
var requestSize = '50mb'
var requestType = 'audio/wav'
const MICSTATUS = {
OFF : '1',
ON : '2',
CONTEXTERROR : '4'
}
function Node (config) {
RED.nodes.createNode(this, config);
var node = this;
RED.httpAdmin.get('/node-red-microphone/status', function (req, res) {
var n = RED.nodes.getNode(req.query.id)
var status = {};
switch(req.query.status) {
case MICSTATUS.ON :
status = {fill:'red', shape:'dot', text:'recording...'}
break;
case MICSTATUS.CONTEXTERROR :
status = {fill:'red', shape:'dot', text:'error resuming audio context'}
break;
}
if (n) {
n.status(status);
}
res.json({});
});
RED.httpAdmin.post('/node-red-microphone/:id', bodyParser.raw({ type: requestType, limit: requestSize }), function(req,res) {
var node = RED.nodes.getNode(req.params.id)
if (node != null) {
try {
node.receive({payload: req.body})
res.sendStatus(200)
} catch(err) {
node.status({fill:'red', shape:'dot', text:'upload failed'});
res.sendStatus(500)
node.error(RED._("upload-microphone.failed", { error: err.toString() }))
}
} else {
res.status(404).send("no node found")
}
})
this.on('input', function (msg) {
if(msg.payload !== '') {
node.send(msg)
}
})
}
RED.nodes.registerType('microphone', Node)
RED.httpAdmin.get('/microphone/js/*', function(req, res){
var options = {
root: __dirname + '/static/',
dotfiles: 'deny'
};
res.sendFile(req.params[0], options);
});
};

View File

@@ -0,0 +1,357 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Recorder = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
module.exports = require("./recorder").Recorder;
},{"./recorder":2}],2:[function(require,module,exports){
'use strict';
var _createClass = (function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];descriptor.enumerable = descriptor.enumerable || false;descriptor.configurable = true;if ("value" in descriptor) descriptor.writable = true;Object.defineProperty(target, descriptor.key, descriptor);
}
}return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);return Constructor;
};
})();
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Recorder = undefined;
var _inlineWorker = require('inline-worker');
var _inlineWorker2 = _interopRequireDefault(_inlineWorker);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var Recorder = exports.Recorder = (function () {
function Recorder(source, cfg) {
var _this = this;
_classCallCheck(this, Recorder);
this.config = {
bufferLen: 4096,
numChannels: 2,
mimeType: 'audio/wav'
};
this.recording = false;
this.callbacks = {
getBuffer: [],
exportWAV: []
};
Object.assign(this.config, cfg);
this.context = source.context;
this.node = (this.context.createScriptProcessor || this.context.createJavaScriptNode).call(this.context, this.config.bufferLen, this.config.numChannels, this.config.numChannels);
this.node.onaudioprocess = function (e) {
if (!_this.recording) return;
var buffer = [];
for (var channel = 0; channel < _this.config.numChannels; channel++) {
buffer.push(e.inputBuffer.getChannelData(channel));
}
_this.worker.postMessage({
command: 'record',
buffer: buffer
});
};
source.connect(this.node);
this.node.connect(this.context.destination); //this should not be necessary
var self = {};
this.worker = new _inlineWorker2.default(function () {
var recLength = 0,
recBuffers = [],
sampleRate = undefined,
numChannels = undefined;
self.onmessage = function (e) {
switch (e.data.command) {
case 'init':
init(e.data.config);
break;
case 'record':
record(e.data.buffer);
break;
case 'exportWAV':
exportWAV(e.data.type);
break;
case 'getBuffer':
getBuffer();
break;
case 'clear':
clear();
break;
}
};
function init(config) {
sampleRate = config.sampleRate;
numChannels = config.numChannels;
initBuffers();
}
function record(inputBuffer) {
for (var channel = 0; channel < numChannels; channel++) {
recBuffers[channel].push(inputBuffer[channel]);
}
recLength += inputBuffer[0].length;
}
function exportWAV(type) {
var buffers = [];
for (var channel = 0; channel < numChannels; channel++) {
buffers.push(mergeBuffers(recBuffers[channel], recLength));
}
var interleaved = undefined;
if (numChannels === 2) {
interleaved = interleave(buffers[0], buffers[1]);
} else {
interleaved = buffers[0];
}
var dataview = encodeWAV(interleaved);
var audioBlob = new Blob([dataview], { type: type });
self.postMessage({ command: 'exportWAV', data: audioBlob });
}
function getBuffer() {
var buffers = [];
for (var channel = 0; channel < numChannels; channel++) {
buffers.push(mergeBuffers(recBuffers[channel], recLength));
}
self.postMessage({ command: 'getBuffer', data: buffers });
}
function clear() {
recLength = 0;
recBuffers = [];
initBuffers();
}
function initBuffers() {
for (var channel = 0; channel < numChannels; channel++) {
recBuffers[channel] = [];
}
}
function mergeBuffers(recBuffers, recLength) {
var result = new Float32Array(recLength);
var offset = 0;
for (var i = 0; i < recBuffers.length; i++) {
result.set(recBuffers[i], offset);
offset += recBuffers[i].length;
}
return result;
}
function interleave(inputL, inputR) {
var length = inputL.length + inputR.length;
var result = new Float32Array(length);
var index = 0,
inputIndex = 0;
while (index < length) {
result[index++] = inputL[inputIndex];
result[index++] = inputR[inputIndex];
inputIndex++;
}
return result;
}
function floatTo16BitPCM(output, offset, input) {
for (var i = 0; i < input.length; i++, offset += 2) {
var s = Math.max(-1, Math.min(1, input[i]));
output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
}
function writeString(view, offset, string) {
for (var i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
}
function encodeWAV(samples) {
var buffer = new ArrayBuffer(44 + samples.length * 2);
var view = new DataView(buffer);
/* RIFF identifier */
writeString(view, 0, 'RIFF');
/* RIFF chunk length */
view.setUint32(4, 36 + samples.length * 2, true);
/* RIFF type */
writeString(view, 8, 'WAVE');
/* format chunk identifier */
writeString(view, 12, 'fmt ');
/* format chunk length */
view.setUint32(16, 16, true);
/* sample format (raw) */
view.setUint16(20, 1, true);
/* channel count */
view.setUint16(22, numChannels, true);
/* sample rate */
view.setUint32(24, sampleRate, true);
/* byte rate (sample rate * block align) */
view.setUint32(28, sampleRate * 4, true);
/* block align (channel count * bytes per sample) */
view.setUint16(32, numChannels * 2, true);
/* bits per sample */
view.setUint16(34, 16, true);
/* data chunk identifier */
writeString(view, 36, 'data');
/* data chunk length */
view.setUint32(40, samples.length * 2, true);
floatTo16BitPCM(view, 44, samples);
return view;
}
}, self);
this.worker.postMessage({
command: 'init',
config: {
sampleRate: this.context.sampleRate,
numChannels: this.config.numChannels
}
});
this.worker.onmessage = function (e) {
var cb = _this.callbacks[e.data.command].pop();
if (typeof cb == 'function') {
cb(e.data.data);
}
};
}
_createClass(Recorder, [{
key: 'record',
value: function record() {
this.recording = true;
}
}, {
key: 'stop',
value: function stop() {
this.recording = false;
}
}, {
key: 'clear',
value: function clear() {
this.worker.postMessage({ command: 'clear' });
}
}, {
key: 'getBuffer',
value: function getBuffer(cb) {
cb = cb || this.config.callback;
if (!cb) throw new Error('Callback not set');
this.callbacks.getBuffer.push(cb);
this.worker.postMessage({ command: 'getBuffer' });
}
}, {
key: 'exportWAV',
value: function exportWAV(cb, mimeType) {
mimeType = mimeType || this.config.mimeType;
cb = cb || this.config.callback;
if (!cb) throw new Error('Callback not set');
this.callbacks.exportWAV.push(cb);
this.worker.postMessage({
command: 'exportWAV',
type: mimeType
});
}
}], [{
key: 'forceDownload',
value: function forceDownload(blob, filename) {
var url = (window.URL || window.webkitURL).createObjectURL(blob);
var link = window.document.createElement('a');
link.href = url;
link.download = filename || 'output.wav';
var click = document.createEvent("Event");
click.initEvent("click", true, true);
link.dispatchEvent(click);
}
}]);
return Recorder;
})();
exports.default = Recorder;
},{"inline-worker":3}],3:[function(require,module,exports){
"use strict";
module.exports = require("./inline-worker");
},{"./inline-worker":4}],4:[function(require,module,exports){
(function (global){
"use strict";
var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } };
var WORKER_ENABLED = !!(global === global.window && global.URL && global.Blob && global.Worker);
var InlineWorker = (function () {
function InlineWorker(func, self) {
var _this = this;
_classCallCheck(this, InlineWorker);
if (WORKER_ENABLED) {
var functionBody = func.toString().trim().match(/^function\s*\w*\s*\([\w\s,]*\)\s*{([\w\W]*?)}$/)[1];
var url = global.URL.createObjectURL(new global.Blob([functionBody], { type: "text/javascript" }));
return new global.Worker(url);
}
this.self = self;
this.self.postMessage = function (data) {
setTimeout(function () {
_this.onmessage({ data: data });
}, 0);
};
setTimeout(function () {
func.call(self);
}, 0);
}
_createClass(InlineWorker, {
postMessage: {
value: function postMessage(data) {
var _this = this;
setTimeout(function () {
_this.self.onmessage({ data: data });
}, 0);
}
}
});
return InlineWorker;
})();
module.exports = InlineWorker;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{}]},{},[1])(1)
});

View File

@@ -0,0 +1,51 @@
{
"name": "node-red-contrib-browser-utils",
"version": "0.0.11",
"description": "A collection of Node-RED nodes for browser interaction",
"dependencies": {
"node-red-contrib-play-audio": "^2.5.0",
"body-parser": "^1.20.0"
},
"license": "Apache-2.0",
"keywords": [
"node-red",
"microphone",
"camera",
"upload",
"bluemix",
"watson"
],
"repository": {
"type": "git",
"url": "https://github.com/ibm-early-programs/node-red-contrib-browser-utils"
},
"contributors": [
{
"name": "Chris Parsons",
"email": "christopherkparsons@gmail.com"
},
{
"name": "Yacine Rezgui",
"email": "rezgui.y@gmail.com"
},
{
"name": "Gibson Fahnestock",
"email": "gibfahn@gmail.com"
},
{
"name": "Anna Thomas",
"email": "annat21@live.co.uk"
},
{
"name": "Soheel Chughtai",
"email": "soheel_chughtai@uk.ibm.com"
}
],
"node-red": {
"nodes": {
"fileinject": "./fileinject/fileinject.js",
"microphone": "./microphone/microphone.js",
"camera": "./camera/camera.js"
}
}
}