Video stream from Blob NodeJS - node.js

I am recording MediaStream on client side in this way:
handleStream(stream) {
const ws = new WebSocket('ws://localhost:5432/binary');
var recorder = new MediaRecorder(stream);
recorder.ondataavailable = function(event) {
ws.send(event.data);
};
recorder.start();
}
This data are accepted on server side like this:
const wss = new WebSocket.Server({ port: 5432 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
writeToDisk(message, 'video.webm');
});
});
function writeToDisk(dataURL, fileName) {
var fileBuffer = new Buffer(dataURL, 'base64');
fs.writeFileSync(fileName, fileBuffer);
}
It works like a charm, but I want to take the Buffer and make video live stream served by server side. Is there any way how to do it?
Thanks for your help.

I have already done this here.
You can use the MediaRecorder class to split the video into chunks and send them to the server for broadcast.
this._mediaRecorder = new MediaRecorder(this._stream, this._streamOptions);
this._mediaRecorder.ondataavailable = e => this._videoStreamer.pushChunk(e.data);
this._mediaRecorder.start();
...
this._mediaRecorder.requestData()
Do not forget to restart recording at intervals, so that new clients should not download all the video to connect to stream. Also, during the change of chunks, you should replace <video> by <image> or update video's poster so that the gluing goes smoothly.
async function imageBitmapToBlob(img) {
return new Promise(res => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
canvas.getContext('2d').drawImage(img,0,0);
canvas.toBlob(res);
});
}
...
const stream = document.querySelector('video').captureStream();
if(stream.active==true) {
const track = stream.getVideoTracks()[0];
const capturer = new ImageCapture(track);
const bitmap = await imageBitmapToBlob(await capturer.grabFrame());
URL.revokeObjectURL(this._oldPosterUrl);
this._video.poster = this._oldPosterUrl = URL.createObjectURL(bitmap);
track.stop();
}
You can glue Blob objects through their constructor. In the process of obtaining a new chunk, do not forget to clear the memory for the old video with URL.revokeObjectURL() and update video's current time
_updateVideo = async (newBlob = false) => {
const stream = this._video.captureStream();
if(stream.active==true) {
const track = stream.getVideoTracks()[0];
const capturer = new ImageCapture(track);
const bitmap = await imageBitmapToBlob(await capturer.grabFrame());
URL.revokeObjectURL(this._oldPosterUrl);
this._video.poster = this._oldPosterUrl = URL.createObjectURL(bitmap);
track.stop();
}
let data = null;
if(newBlob === true) {
const index = this._recordedChunks.length - 1;
data = [this._recordedChunks[index]];
} else {
data = this._recordedChunks;
}
const blob = new Blob(data, this._options);
const time = this._video.currentTime;
URL.revokeObjectURL(this._oldVideoUrl);
const url = this._oldVideoUrl = URL.createObjectURL(blob);
if(newBlob === true) {
this._recordedChunks = [blob];
}
this._size = blob.size;
this._video.src = url;
this._video.currentTime = time;
}
You should use two WebSocket for video broadcast and two for listening. One WebSocket transfers only video chunks, the second only new blobs with video headers (restart recording at intervals).
const blobWebSocket = new WebSocket(`ws://127.0.0.1:${blobPort}/`);
blobWebSocket.onmessage = (e) => {
console.log({blob:e.data});
this._videoWorker.pushBlob(e.data);
}
const chunkWebSocket = new WebSocket(`ws://127.0.0.1:${chunkPort}/`);
chunkWebSocket.onmessage = (e) => {
console.log({chunk:e.data});
this._videoWorker.pushChunk(e.data);
}
After connecting, the server sends the client all the current video blob and begins to dynamically send new chunks to the client.
const wss = new WebSocket.Server({ port });
let buffer = new Buffer.alloc(0);
function chunkHandler(buf,isBlob=false) {
console.log({buf,isBlob});
if(isBlob === true) {
//broadcast(wss,buf);
buffer = buf;
} else {
const totalLenght = buffer.length + buf.length;
buffer = Buffer.concat([buffer,buf],totalLenght);
broadcast(wss,buf);
}
}
wss.on('connection', function connection(ws) {
if(buffer.length !== 0) {
ws.send(buffer);
}
});

Related

Write data from Socket.io to Google Cloud Storage

I get audio data from this package in my React Native project and I then send the data chunks over socket.io to my Node.js server.
AudioRecord.on('data', data => {
// base64-encoded audio data chunks
const binary = Buffer.from(data, 'base64').toString('utf-8')
socketClient.io.emit('binaryData', binary)
})
The data that my server receives is readable because I send that data through this transform onto a speech recognition service that returns accurate results.
const speechAudioInputStreamTransform = new Transform({
transform: (chunk, encoding, callback) => {
if (newStream && lastAudioInput.length !== 0) {
// Approximate math to calculate time of chunks
const chunkTime = streamingLimit / lastAudioInput.length
if (chunkTime !== 0) {
if (bridgingOffset < 0) {
bridgingOffset = 0
}
if (bridgingOffset > finalRequestEndTime) {
bridgingOffset = finalRequestEndTime
}
const chunksFromMS = Math.floor(
(finalRequestEndTime - bridgingOffset) / chunkTime
)
bridgingOffset = Math.floor(
(lastAudioInput.length - chunksFromMS) * chunkTime
)
for (let i = chunksFromMS; i < lastAudioInput.length; i++) {
if (recognizeStream) {
recognizeStream.write(lastAudioInput[i])
}
}
}
newStream = false
}
audioInput.push(chunk)
if (recognizeStream) {
recognizeStream.write(chunk)
}
callback()
},
})
However when I write this data to Google Cloud Storage the file saves and contains data but seems corrupted as it is unplayable.
const { Storage } = require('#google-cloud/storage')
const storage = new Storage(storageOptions)
const bucket = storage.bucket(bucketName)
const file = filename + '.wav'
const storageFile = bucket.file.createWriteStream()
client.on('binaryData', (data) => {
const buffer = Buffer.from(data)
if (recognizeStream != null) {
storageFile.write(buffer)
speechAudioInputStreamTransform.write(buffer)
}
})
When the user on the RN side terminates the session, storageFile.end() is called.
Any help would be greatly appreciated. Thanks

Amazon Transcribe streaming with Node.js using websocket

I am working on a whatsapp chatbot where I receive audio file(ogg format) file url from Whatsapp and I get buffer and upload that file on S3(sample.ogg) Now what is want to use AWS Transcribe Streaming so I am creating readStream of file and sending to AWS transcribe I am using websocket but I am receiving Empty response of Sometimes when I Mhm mm mm response. Please can anyone tell what wrong I am doing in my code
const express = require('express')
const app = express()
const fs = require('fs');
const crypto = require('crypto'); // tot sign our pre-signed URL
const v4 = require('./aws-signature-v4'); // to generate our pre-signed URL
const marshaller = require("#aws-sdk/eventstream-marshaller"); // for converting binary event stream messages to and from JSON
const util_utf8_node = require("#aws-sdk/util-utf8-node");
var WebSocket = require('ws') //for opening a web socket
// our converter between binary event streams messages and JSON
const eventStreamMarshaller = new marshaller.EventStreamMarshaller(util_utf8_node.toUtf8, util_utf8_node.fromUtf8);
// our global variables for managing state
let languageCode;
let region = 'ap-south-1';
let sampleRate;
let inputSampleRate;
let transcription = "";
let socket;
let micStream;
let socketError = false;
let transcribeException = false;
// let languageCode = 'en-us'
app.listen(8081, (error, data) => {
if(!error) {
console.log(`running at 8080----->>>>`)
}
})
let handleEventStreamMessage = function (messageJson) {
let results = messageJson.Transcript.Results;
if (results.length > 0) {
if (results[0].Alternatives.length > 0) {
let transcript = results[0].Alternatives[0].Transcript;
// fix encoding for accented characters
transcript = decodeURIComponent(escape(transcript));
console.log(`Transcpted is----->>${transcript}`)
}
}
}
function downsampleBuffer (buffer, inputSampleRate = 44100, outputSampleRate = 16000){
if (outputSampleRate === inputSampleRate) {
return buffer;
}
var sampleRateRatio = inputSampleRate / outputSampleRate;
var newLength = Math.round(buffer.length / sampleRateRatio);
var result = new Float32Array(newLength);
var offsetResult = 0;
var offsetBuffer = 0;
while (offsetResult < result.length) {
var nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
var accum = 0,
count = 0;
for (var i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++ ) {
accum += buffer[i];
count++;
}
result[offsetResult] = accum / count;
offsetResult++;
offsetBuffer = nextOffsetBuffer;
}
return result;
}
function pcmEncode(input) {
var offset = 0;
var buffer = new ArrayBuffer(input.length * 2);
var view = new DataView(buffer);
for (var i = 0; i < input.length; i++, offset += 2) {
var s = Math.max(-1, Math.min(1, input[i]));
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true);
}
return buffer;
}
function getAudioEventMessage(buffer) {
// wrap the audio data in a JSON envelope
return {
headers: {
':message-type': {
type: 'string',
value: 'event'
},
':event-type': {
type: 'string',
value: 'AudioEvent'
}
},
body: buffer
};
}
function convertAudioToBinaryMessage(raw) {
if (raw == null)
return;
// downsample and convert the raw audio bytes to PCM
let downsampledBuffer = downsampleBuffer(raw, inputSampleRate);
let pcmEncodedBuffer = pcmEncode(downsampledBuffer);
setTimeout(function() {}, 1);
// add the right JSON headers and structure to the message
let audioEventMessage = getAudioEventMessage(Buffer.from(pcmEncodedBuffer));
//convert the JSON object + headers into a binary event stream message
let binary = eventStreamMarshaller.marshall(audioEventMessage);
return binary;
}
function createPresignedUrl() {
let endpoint = "transcribestreaming." + "us-east-1" + ".amazonaws.com:8443";
// get a preauthenticated URL that we can use to establish our WebSocket
return v4.createPresignedURL(
'GET',
endpoint,
'/stream-transcription-websocket',
'transcribe',
crypto.createHash('sha256').update('', 'utf8').digest('hex'), {
'key': <AWS_KEY>,
'secret': <AWS_SECRET_KEY>,
'protocol': 'wss',
'expires': 15,
'region': 'us-east-1',
'query': "language-code=" + 'en-US' + "&media-encoding=pcm&sample-rate=" + 8000
}
);
}
function showError(message) {
console.log("Error: ",message)
}
app.get('/convert', (req, res) => {
var file = 'recorded.mp3'
const eventStreamMarshaller = new marshaller.EventStreamMarshaller(util_utf8_node.toUtf8, util_utf8_node.fromUtf8);
let url = createPresignedUrl();
let socket = new WebSocket(url);
socket.binaryType = "arraybuffer";
let output = '';
const readStream = fs.createReadStream(file, { highWaterMark: 32 * 256 })
readStream.setEncoding('binary')
//let sampleRate = 0;
let inputSampleRate = 44100
readStream.on('end', function() {
console.log('finished reading----->>>>');
// write to file here.
// Send an empty frame so that Transcribe initiates a closure of the WebSocket after submitting all transcripts
let emptyMessage = getAudioEventMessage(Buffer.from(new Buffer([])));
let emptyBuffer = eventStreamMarshaller.marshall(emptyMessage);
socket.send(emptyBuffer);
})
// when we get audio data from the mic, send it to the WebSocket if possible
socket.onopen = function() {
readStream.on('data', function(chunk) {
let binary = convertAudioToBinaryMessage(chunk);
if (socket.readyState === socket.OPEN) {
console.log(`sending to steaming API------->>>>`)
socket.send(binary);
}
});
// the audio stream is raw audio bytes. Transcribe expects PCM with additional metadata, encoded as binary
}
// the audio stream is raw audio bytes. Transcribe expects PCM with additional metadata, encoded as binary
socket.onerror = function () {
socketError = true;
showError('WebSocket connection error. Try again.');
};
// handle inbound messages from Amazon Transcribe
socket.onmessage = function (message) {
//convert the binary event stream message to JSON
let messageWrapper = eventStreamMarshaller.unmarshall(Buffer(message.data));
//console.log(`messag -->>${JSON.stringify(messageWrapper)}`)
let messageBody = JSON.parse(String.fromCharCode.apply(String, messageWrapper.body));
console.log("results:.. ",JSON.stringify(messageBody))
if (messageWrapper.headers[":message-type"].value === "event") {
handleEventStreamMessage(messageBody);
}
else {
transcribeException = true;
showError(messageBody.Message);
}
}
let closeSocket = function () {
if (socket.OPEN) {
// Send an empty frame so that Transcribe initiates a closure of the WebSocket after submitting all transcripts
let emptyMessage = getAudioEventMessage(Buffer.from(new Buffer([])));
let emptyBuffer = eventStreamMarshaller.marshall(emptyMessage);
socket.send(emptyBuffer);
}
}
})

Adding image dynamically in public folder in reactjs

I am developing an face detection application,for that I need to collect the users image for reference to detect them later.i have successfully uploaded the image in MySQL databse.now I need upload the image in public folder in react to detect the image in camera.i stuck in uploading image in react public folder.help me out get rid of this problem..
This is the React code where image to be detected in the imgUrl variable
detect = async () => {
const videoTag = document.getElementById("videoTag");
const canvas = document.getElementById("myCanvas");
const displaySize = { width: videoTag.width, height: videoTag.height };
faceapi.matchDimensions(canvas, displaySize);
//setInterval starts here for continuous detection
time = setInterval(async () => {
let fullFaceDescriptions = await faceapi
.detectAllFaces(videoTag)
.withFaceLandmarks()
.withFaceExpressions()
.withFaceDescriptors();
const value = fullFaceDescriptions.length;
this.setState({ detection: value });
fullFaceDescriptions = faceapi.resizeResults(
fullFaceDescriptions,
displaySize
);
canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height);
//Label Images
var dummy = ["praveen", "vikranth", "Gokul", "Rahul"];
const labels = nameArray1;
// const labels = ["praveen", "vikranth", "Gokul", "Rahul"];
if (no_of_times <= 0) {
if (no_of_times === 0) {
labeledFaceDescriptors = await Promise.all(
labels.map(async (label) => {
// fetch image data from urls and convert blob to HTMLImage element
const imgUrl = `/img/${label}.png`; // for testing purpose
// const imgUrl = testImage;
const img = await faceapi.fetchImage(imgUrl);
const fullFaceDescription = await faceapi
.detectSingleFace(img)
.withFaceLandmarks()
.withFaceExpressions()
.withFaceDescriptor();
if (!fullFaceDescription) {
throw new Error(`no faces detected for ${label}`);
}
const faceDescriptors = [fullFaceDescription.descriptor];
return new faceapi.LabeledFaceDescriptors(label, faceDescriptors);
})
);
// console.log(no_of_times);
}
}
const maxDescriptorDistance = 0.7;
no_of_times++;
const faceMatcher = new faceapi.FaceMatcher(
labeledFaceDescriptors,
maxDescriptorDistance
);
const results = fullFaceDescriptions.map((fd) =>
faceMatcher.findBestMatch(fd.descriptor)
);
result = [];
results.forEach((bestMatch, i) => {
const box = fullFaceDescriptions[i].detection.box;
// console.log(box)
const text = bestMatch.toString(); //this for basMatch name detection
var str = "";
//This is for removing names confidence to map value without duplicate
var val = text.replace(/[0-9]/g, "");
for (let i of val) {
if (i !== " ") {
str += i;
} else {
break;
}
}
if (result.includes(str) === false) result.push(str);
const drawBox = new faceapi.draw.DrawBox(box, { label: text });
drawBox.draw(canvas);
faceapi.draw.drawFaceExpressions(canvas, fullFaceDescriptions, 0.85);
});
for (let i = 0; i < fullFaceDescriptions.length; i++) {
const result1 = fullFaceDescriptions[i].expressions.asSortedArray()[i];
// console.log(result[i]);
// console.log(result1.expression);
this.test(result[i], result1.expression);
}
}, 100);
In the above code i am manually putting image in public folder,this need to be done dynamically when the user uploads image.
this is place i get the images in base64 from nodejs
axios.get("/image").then((res) => {
testImage = res.data;
// console.log("from image" + res.data);
imgback = <img src={`data:image/jpeg;base64,${res.data}`} />;
});
This is nodejs code for the get request from reactjs
app.get("/image", (req, res) => {
connection.query("SELECT * FROM images", (error, row, fields) => {
if (!!error) {
console.log("Error in the query");
} else {
console.log("successful query");
var buffer = new Buffer(row[0].image, "binary");
var bufferBase64 = buffer.toString("base64");
res.send(bufferBase64);
}
});
});
my goal is, in the imgUrl variable in react code i need to specify the image folder for that i need to dynamically add image in folder.
Or is there is any other way to directly give image array in the imgUrl variable.please help me to sort out this problem.

new MediaRecorder(stream[, options]) stream can living modify?

new MediaRecorder(stream[, options]);
I want record the user camera and audio
I need mixing the song.mp3 to the audio track in recording.
and result export a video file to download by link.
But the MediaRecorder first params stream can living modify ?
But When I use recoder.stop()
It tips error: Failed to execute 'stop' on 'MediaRecorder': The MediaRecorder's state is 'inactive'.
My code:
function getFileBuffer(filepath) {
return fetch(filepath, {method: 'GET'}).then(response => response.arrayBuffer())
}
function mp3play() {
getFileBuffer('song.mp3')
.then(buffer => context.decodeAudioData(buffer))
.then(buffer => {
console.log(buffer)
const source = context.createBufferSource()
source.buffer = buffer
let volume = context.createGain()
volume.gain.value = 1
source.connect(volume)
dest = context.createMediaStreamDestination()
volume.connect(dest)
// volume.connect(context.destination)
source.start(0)
const _audioTrack = stream.getAudioTracks();
if (_audioTrack.length > 0) {
_audioTrack[0].stop();
stream.removeTrack(_audioTrack[0]);
}
//
// console.log(dest.stream)
// console.log(dest.stream.getAudioTracks()[0])
// stream.addTrack(dest.stream.getAudioTracks()[0])
})
}
function startRecording() {
recorder = new MediaRecorder(stream, {
mimeType: 'video/webm'
})
recorder.start()
stopBtn.removeAttribute('disabled')
startBtn.disabled = true
}
No we still can't record a MediaStream whose tracks are changed after the recording began, doing so will stop() the MediaRecorder. Here is a very related Q/A which was more about recording video.
What can be done though is to create a kind of merger MediaStream.
It's way easier with audio, moreover since you are already using the WebAudio API: all you need to do is to create an other MediaStreamDestination node, and connect / disconnect the different sources.
const base = "https://upload.wikimedia.org/wikipedia/en/d/";
const urls = [
"d3/Beach_Boys_-_Good_Vibrations.ogg",
"dc/Strawberry_Fields_Forever_%28Beatles_song_-_sample%29.ogg"
].map( url => base + url );
const context = new AudioContext();
const button = document.querySelector( 'button' );
button.onclick = async () => {
button.disabled = true;
context.resume();
const audiobuffers = await Promise.all( urls.map( fetchAsAudioBuffer ) );
button.remove();
const streamNode = context.createMediaStreamDestination();
const stream = streamNode.stream;
const recorder = new MediaRecorder( stream );
const chunks = [];
recorder.ondataavailable = evt => chunks.push( evt.data );
recorder.onstop = evt => exportAudio( new Blob( chunks ) );
document.getElementById( 'record-stopper' ).onclick = evt => {
recorder.stop();
current_source.stop( 0 );
};
let current_index = 0;
let current_source = null;
document.getElementById( 'switcher' ).onclick = switchAudioSource;
switchAudioSource();
recorder.start();
function switchAudioSource() {
if( current_source ) {
current_source.stop( 0 );
}
current_index = (current_index + 1) % audiobuffers.length;
current_source = context.createBufferSource();
current_source.buffer = audiobuffers[ current_index ];
current_source.loop = true;
current_source.connect( streamNode );
current_source.connect( context.destination );
current_source.start( 0 );
}
};
function exportAudio( blob ) {
const aud = new Audio( URL.createObjectURL( blob ) );
aud.controls = true;
document.body.prepend( aud );
}
async function fetchAsAudioBuffer( url ) {
const buf = await fetchAsBuffer( url );
return context.decodeAudioData( buf );
}
async function fetchAsBuffer( url ) {
const resp = await fetch( url );
return resp.arrayBuffer();
}
button+.recording-controls,
audio+.recording-controls {
display: none;
}
<button>begin</button>
<div class="recording-controls">
<label>Recording...</label>
<button id="switcher">Switch Audio Sources</button>
<button id="record-stopper">Stop Recording</button>
</div>
For video that would imply recording a CanvasMediaStreamTrack and drawing the different video streams on the source <canvas>, but we generally loose a lot of quality doing so...

Uncompress gzipped http request body to json in Node.js

I have a windows 8 application connecting to a web service written in Node.js. On the windows 8 side I compressed my request body to gzip. But on the Node.js side I found that my req.body type was Object.
I cannot use zlib to uncomporess the body since it's not a stream.
I can use zlib to uncomporess the req, but I don't know how to retrieve the req.body content from the unzipped stream and parse the body in JSON format.
BTW, I reviewed my request through Fiddler and it told me the request body was gzipped, and I can see my raw body through Fiddler after unzipped so the request should be correct.
Updated
Below is my Node.js app
(function () {
var express = require("express");
var zlib = require("zlib");
var app = express();
var port = 12345;
app.configure(function () {
app.use(express.compress());
app.use(express.bodyParser());
});
app.post("/test", function (req, res) {
var request = req.body;
req.pipe(zlib.createGunzip());
var response = {
status: 0,
value: "OK"
};
res.send(200, response);
});
console.log("started at port %d", port);
app.listen(port);
})();
And below is my windows store app code (partial)
private async void button1_Click_1(object sender, RoutedEventArgs e)
{
var message = new
{
Name = "Shaun",
Value = "12345678901234567890123456789012345678901234567890"
};
var json = await JsonConvert.SerializeObjectAsync(message, Formatting.Indented);
var bytes = Encoding.UTF8.GetBytes(json);
var client = new HttpClient();
client.BaseAddress = new Uri("http://192.168.56.1:12345/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.ExpectContinue = false;
var jsonContent = new JsonContent(message);
var gzipContent = new GZipContent3(jsonContent);
var res = await client.PostAsync("test", gzipContent);
var dialog = new Windows.UI.Popups.MessageDialog(":)", "完成");
await dialog.ShowAsync();
}
internal class GZipContent3 : ByteArrayContent
{
public GZipContent3(HttpContent content)
: base(LoadGZipBytes(content))
{
//base.Headers.ContentType = content.Headers.ContentType;
base.Headers.ContentType = new MediaTypeHeaderValue("x-application/x-gzip");
base.Headers.ContentEncoding.Add("gzip");
}
private static byte[] LoadGZipBytes(HttpContent content)
{
var source = content.ReadAsByteArrayAsync().Result;
byte[] buffer;
using (var outStream = new MemoryStream())
{
using (var gzip = new GZipStream(outStream, CompressionMode.Compress, true))
{
gzip.Write(source, 0, source.Length);
}
buffer = outStream.ToArray();
}
return buffer;
}
}
internal class JsonContent : StringContent
{
private const string defaultMediaType = "application/json";
public JsonContent(string json)
: base(json)
{
var mediaTypeHeaderValue = new MediaTypeHeaderValue(defaultMediaType);
mediaTypeHeaderValue.CharSet = Encoding.UTF8.WebName;
base.Headers.ContentType = mediaTypeHeaderValue;
}
public JsonContent(object content)
: this(GetJson(content))
{
}
private static string GetJson(object content)
{
if (content == null)
{
throw new ArgumentNullException("content");
}
var json = JsonConvert.SerializeObject(content, Formatting.Indented);
return json;
}
}
http://www.senchalabs.org/connect/json.html. Basically you need to write your own middleware based on connect.json() that pipes through an uncompression stream like connect.compress() but the opposite direction: http://www.senchalabs.org/connect/compress.html
Also, make sure you're sending the correct Content-Encoding header in your request.
If you show me what you have so far I can help you further.
I was working on similar thing and finally landed on
function getGZipped(req, callback) {
var gunzip = zlib.createGunzip();
req.pipe(gunzip);
var buffer = [];
gunzip.on('data', function (data) {
// decompression chunk ready, add it to the buffer
buffer.push(data);
}).on('end', function () {
//response and decompression complete, join the buffer and return
callback(null, JSON.parse(buffer));
}).on('error', function (e) {
callback(e);
});
}

Resources