Amazon Transcribe streaming with Node.js using websocket - node.js

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);
}
}
})

Related

Messages are large, You may want to consider smaller messages. node-ipc and swift (OSX)

I have a node app that is trying to communicate with a swift application using a unix socket.
I am broadcasting a Message ever 3 seconds using the following code with node-ipc:
const ipc = require('node-ipc').default;
// Run IPC server for testing
const first = "FIRST";
const second = "SECOND";
const ipcSocketPath = `/tmp/${ipcAppId}`;
async function setupCoreCommunicationIpc() {
await new Promise((resolve) => {
ipc.serve(ipcSocketPath, () => {
ipc.server.on('ping', (data, socket) => {
ipc.server.emit(socket, 'pong');
});
return resolve(true);
});
ipc.server.start();
});
}
function sendMessage(message, payload = {}) {
ipc.server.broadcast(message, { ...payload, "valueOne": first, "valueTwo": seconds });
}
(async () => {
try {
await setupCoreCommunicationIpc()
} catch (e) {
// Deal with the fact the chain failed
}
// `text` is not available here
})();
setInterval(async () => {
await sendMessage("first:message", {core_id: ipcAppId, app_id: ipcAppId, message_id: 5})
}, 3000);
The code on swift is a bit more complicated. But I am able to connect to the unix socket and receive the message. The Problem is the sending of the AckMessage. I tried different approaches for sending the message but it would not work. Here is the code in swift:
func startSocketStack(valueOne: String, valueTwo: String){
let MTU = 65536
let path = "/tmp/\(valueTwo)"
print("starting socket at: %\(path)%")
let client = socket(AF_UNIX, SOCK_STREAM, 0)
var address = sockaddr_un()
//address.sun_family = UInt8(AF_UNIX)
address.sun_family = sa_family_t(AF_UNIX)
//address.sun_len = UInt8(MemoryLayout<sockaddr_un>.size)
address.sun_len = UInt8(MemoryLayout<UInt8>.size + MemoryLayout<sa_family_t>.size + path.utf8.count + 1)
strlcpy(&address.sun_path.0, path, MemoryLayout.size(ofValue: address.sun_path))
var adressUnwrap = address
withUnsafePointer(to: &adressUnwrap) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
let connection = connect(client, $0, socklen_t(address.sun_len))
if (connection != 0) {
print("startSocket: could not connect to socket at \(path)")
}
}
}
var buffer = UnsafeMutableRawPointer.allocate(byteCount: MTU,alignment: MemoryLayout<CChar>.size)
while(true) {
let readResult = read(client, &buffer, MTU)
if (readResult == 0) {
break; // end of file
} else if (readResult == -1) {
print("Error reading form client\(client) - \(errno)")
break; // error
} else {
let strResult = withUnsafePointer(to: &buffer) {
$0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: readResult)) {
String(cString: $0)
}
}
print("Received form client(\(client)): §\(strResult)§")
print("§\(strResult)§")
let trimmedString = strResult.components(separatedBy: .whitespacesAndNewlines).joined()
print("§\(trimmedString)§")
struct Message: Decodable {
let type: String
let data: MessageData
struct MessageData: Decodable {
var valueOne: String
var valueTwo: String
var message_id: String
}
}
struct AckMessage: Encodable {
let type: String
let data: Bool
}
do {
let jsonMessage = trimmedString.components(separatedBy: "}")[0] + "}" + "}"
let jsonData = jsonMessage.trimmingCharacters(in: CharacterSet.newlines).data(using: .utf8)!
let receivedMessage: Message = try JSONDecoder().decode(Message.self, from: jsonData)
let messageId = receivedMessage.data.message_id
let ackMessage = AckMessage(type: messageId, data: true)
let jsonAckMessage = try JSONEncoder().encode(ackMessage)
let delimiter = #"\f"#.bytes
let jsonAckMessageWithDelimiter = jsonAckMessage + delimiter
print("jsonAckMessageWithDelimiter")
print(jsonAckMessageWithDelimiter)
// first attempt
do {
try jsonAckMessageWithDelimiter.withUnsafeBytes() { [unowned self] (buffer: UnsafeRawBufferPointer) throws -> Int in
let buffer = buffer.baseAddress!
print(buffer)
let bufSize = jsonAckMessageWithDelimiter.count
print(bufSize)
var sent = 0
var sendFlags: Int32 = 0
while sent < bufSize {
var s = 0
s = send(client, buffer.advanced(by: sent), Int(bufSize - sent), sendFlags)
if s <= 0 {
if errno == EAGAIN{
// We have written out as much as we can...
return sent
}
}
sent += s
}
return sent
}
} catch {
print(error)
}
// second attempt
jsonAckMessageWithDelimiter.withUnsafeBytes {
guard let pointer = $0.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
write(client, pointer, jsonAckMessageWithDelimiter.count)
}
} catch {
print("Error on received message \(error)")
}
}
}
}
In my node application I can not receive the AckMessage because I get the following error:
Messages are large, You may want to consider smaller messages.
Now from my experience from Windows and Linux I know that \f is the delimiter that should indicate the end of the message. I am sending it at the end of my json but it is somehow not recognized.
What could be the problem? Is the delimiter wrong? Is the buffersize a Problem?
When logging what I send it says that it has sent all 61 bytes as expected.
For anyone having this problem using "\u{000C}" instead of "\f" is the solution.

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

Video stream from Blob NodeJS

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);
}
});

nodejs base64 to blob conversion

Iam capturing webcam screenshot in reactjs(react-webcam). Screenshot is in the form of base64 encoded string. I am sending the base 64string to nodejs and I want to convert base64 String to .jpeg file, So that I can save in Azure Blob Storage.
Is there any method to convert base64 string to .jpeg file.
You can convert your Base64 string to Buffer and then try storing it to azure.
var base64String = "....."; // your base64 string
var bufferValue = Buffer.from(base64String,"base64");
I used this and it worked.
below is server side code(NodeJS)
var contentType = 'image/jpeg';
let base64String=req.body.img;
let base64Image = base64String.split(';base64,').pop();
let date=Date.now();
fs.writeFile(`./uploads/${date}.jpeg`, base64Image, {encoding: 'base64'}, function(err) {
console.log('File created');
sourceFilePath= path.resolve(`./uploads/${date}.jpeg`);
blobName=path.basename(sourceFilePath, path.extname(sourceFilePath));
//console.log(sourceFilePath);
blobService.createBlockBlobFromLocalFile(containerName, blobName, sourceFilePath, err => {
if (err) {
console.log(err);
}
else {
//resolve({ message: `Upload of '${blobName}' complete` });
console.log("UPLOADED")
}
});
Try this:
based-blob
(async function() {
const b = require('based-blob');
const base64String = 'some base64 data...';
const blob = b.toBlob(base64String);
const b64s = await b.toBase64(blob);
console.log(b64s == base64String); // true
})();
Hi i use this function
public b64toBlob = (b64Data: string = '', sliceSize?: number) => {
sliceSize = sliceSize || 512;
if ( b64Data !== null) {
let block = b64Data.split(';');
let dataType = block[0].split(':')[1];
let realData = block[1].split(',')[1];
let filename = this.makeid() + '.' + dataType.split('/')[1];
let byteCharacters = atob(realData);
let byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
let slice = byteCharacters.slice(offset, offset + sliceSize);
let byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
let byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
let blob = new Blob(byteArrays, {type: dataType});
return blob ;
} else {
return '';
}
}

Streaming a file from server to client with socket.io-stream

I've managed to upload files in chunk from a client to a server, but now i want to achieve the opposite way. Unfortunately the documentation on the offical module page lacks for this part.
I want to do the following:
emit a stream and 'download'-event with the filename to the server
the server should create a readstream and pipe it to the stream emitted from the client
when the client reaches the stream, a download-popup should appear and ask where to save the file
The reason why i don't wanna use simple file-hyperlinks is obfuscating: the files on the server are encrpted and renamed, so i have to decrypt and rename them for each download request.
Any code snippets around to get me started with this?
This is a working example I'm using. But somehow (maybe only in my case) this can be very slow.
//== Server Side
ss(socket).on('filedownload', function (stream, name, callback) {
//== Do stuff to find your file
callback({
name : "filename",
size : 500
});
var MyFileStream = fs.createReadStream(name);
MyFileStream.pipe(stream);
});
//== Client Side
/** Download a file from the object store
* #param {string} name Name of the file to download
* #param {string} originalFilename Overrules the file's originalFilename
* #returns {$.Deferred}
*/
function downloadFile(name, originalFilename) {
var deferred = $.Deferred();
//== Create stream for file to be streamed to and buffer to save chunks
var stream = ss.createStream(),
fileBuffer = [],
fileLength = 0;
//== Emit/Request
ss(mysocket).emit('filedownload', stream, name, function (fileError, fileInfo) {
if (fileError) {
deferred.reject(fileError);
} else {
console.log(['File Found!', fileInfo]);
//== Receive data
stream.on('data', function (chunk) {
fileLength += chunk.length;
var progress = Math.floor((fileLength / fileInfo.size) * 100);
progress = Math.max(progress - 2, 1);
deferred.notify(progress);
fileBuffer.push(chunk);
});
stream.on('end', function () {
var filedata = new Uint8Array(fileLength),
i = 0;
//== Loop to fill the final array
fileBuffer.forEach(function (buff) {
for (var j = 0; j < buff.length; j++) {
filedata[i] = buff[j];
i++;
}
});
deferred.notify(100);
//== Download file in browser
downloadFileFromBlob([filedata], originalFilename);
deferred.resolve();
});
}
});
//== Return
return deferred;
}
var downloadFileFromBlob = (function () {
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
return function (data, fileName) {
var blob = new Blob(data, {
type : "octet/stream"
}),
url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
};
}());
Answer My dear friend Jeffrey van Norden That's right. it worked for me. But there was a small bug so I changed the server side code this way:
//== Server Side
ss(socket).on('filedownload', function (stream, name, callback) {
//== Do stuff to find your file
try {
let stats = fs.statSync(name);
let size = stats.size;
callback(false,{
name: name,
size: size
});
let MyFileStream = fs.createReadStream(name);
MyFileStream.pipe(stream);
}
catch (e){
callback(true,{});
}
});
//== Client Side
/** Download a file from the object store
* #param {string} name Name of the file to download
* #param {string} originalFilename Overrules the file's originalFilename
* #returns {$.Deferred}
*/
function downloadFile(name, originalFilename) {
var deferred = $.Deferred();
//== Create stream for file to be streamed to and buffer to save chunks
var stream = ss.createStream(),
fileBuffer = [],
fileLength = 0;
//== Emit/Request
ss(mysocket).emit('filedownload', stream, name, function (fileError, fileInfo) {
if (fileError) {
deferred.reject(fileError);
} else {
console.log(['File Found!', fileInfo]);
//== Receive data
stream.on('data', function (chunk) {
fileLength += chunk.length;
var progress = Math.floor((fileLength / fileInfo.size) * 100);
progress = Math.max(progress - 2, 1);
deferred.notify(progress);
fileBuffer.push(chunk);
});
stream.on('end', function () {
var filedata = new Uint8Array(fileLength),
i = 0;
//== Loop to fill the final array
fileBuffer.forEach(function (buff) {
for (var j = 0; j < buff.length; j++) {
filedata[i] = buff[j];
i++;
}
});
deferred.notify(100);
//== Download file in browser
downloadFileFromBlob([filedata], originalFilename);
deferred.resolve();
});
}
});
//== Return
return deferred;
}
var downloadFileFromBlob = (function () {
var a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
return function (data, fileName) {
var blob = new Blob(data, {
type : "octet/stream"
}),
url = window.URL.createObjectURL(blob);
a.href = url;
a.download = fileName;
a.click();
window.URL.revokeObjectURL(url);
};
}());

Resources