HTML5 read video metadata of mp4 - javascript

Using HTML5 I am trying to get the attribute (ie rotation), located in the header of a mp4 (I play it using a video tag), to do this I am trying to get the bytes that make up the header, and knowing the structure, find this atom.
Does anyone know how to do this in javascript?

You can use mediainfo.js,
It's a porting of mediainfo (cpp) in javascript compiled with emsciptem.
Here is a working example: https://mediainfo.js.org/
You will need to include the js/mediainfo.js file and put mediainfo.js.mem file in the same folder.
You need to check the sources on this file to see how it works:
https://mediainfo.js.org/js/mediainfopage.js
[...]
function parseFile(file) {
if (processing) {
return;
}
processing = true;
[...]
var fileSize = file.size, offset = 0, state = 0, seekTo = -1, seek = null;
mi.open_buffer_init(fileSize, offset);
var processChunk = function(e) {
var l;
if (e.target.error === null) {
var chunk = new Uint8Array(e.target.result);
l = chunk.length;
state = mi.open_buffer_continue(chunk, l);
var seekTo = -1;
var seekToLow = mi.open_buffer_continue_goto_get_lower();
var seekToHigh = mi.open_buffer_continue_goto_get_upper();
if (seekToLow == -1 && seekToHigh == -1) {
seekTo = -1;
} else if (seekToLow < 0) {
seekTo = seekToLow + 4294967296 + (seekToHigh * 4294967296);
} else {
seekTo = seekToLow + (seekToHigh * 4294967296);
}
if(seekTo === -1){
offset += l;
}else{
offset = seekTo;
mi.open_buffer_init(fileSize, seekTo);
}
chunk = null;
} else {
var msg = 'An error happened reading your file!';
console.err(msg, e.target.error);
processingDone();
alert(msg);
return;
}
// bit 4 set means finalized
if (state&0x08) {
var result = mi.inform();
mi.close();
addResult(file.name, result);
processingDone();
return;
}
seek(l);
};
function processingDone() {
processing = false;
$status.hide();
$cancel.hide();
$dropcontrols.fadeIn();
$fileinput.val('');
}
seek = function(length) {
if (processing) {
var r = new FileReader();
var blob = file.slice(offset, length + offset);
r.onload = processChunk;
r.readAsArrayBuffer(blob);
}
else {
mi.close();
processingDone();
}
};
// start
seek(CHUNK_SIZE);
}
[...]
// init mediainfo
miLib = MediaInfo(function() {
console.debug('MediaInfo ready');
$loader.fadeOut(function() {
$dropcontrols.fadeIn();
window['miLib'] = miLib; // debug
mi = new miLib.MediaInfo();
$fileinput.on('change', function(e) {
var el = $fileinput.get(0);
if (el.files.length > 0) {
parseFile(el.files[0]);
}
});
});
Here is the Github address with the sources of the project: https://github.com/buzz/mediainfo.js

I do not think you can extract such detailed metadata from a video, using HTML5 and its video-tag. The only things you can extract (video length, video tracks, etc.) are listed here:
http://www.w3schools.com/tags/ref_av_dom.asp
Of course, there might be special additional methods available in some browsers, but there is no "general" approach - you would need more than the existing methods of HTML5.

Related

count total number of word in pdf book using node js? [duplicate]

I wonder if is possible to get the text inside of a PDF file by using only Javascript?
If yes, can anyone show me how?
I know there are some server-side java, c#, etc libraries but I would prefer not using a server.
thanks
Because pdf.js has been developing over the years, I would like to give a new answer. That is, it can be done locally without involving any server or external service. The new pdf.js has a function: page.getTextContent(). You can get the text content from that. I've done it successfully with the following code.
What you get in each step is a promise. You need to code this way: .then( function(){...}) to proceed to the next step.
PDFJS.getDocument( data ).then( function(pdf) {
pdf.getPage(i).then( function(page){
page.getTextContent().then( function(textContent){
What you finally get is an string array textContent.bidiTexts[]. You concatenate them to get the text of 1 page. Text blocks' coordinates are used to judge whether newline or space need to be inserted. (This may not be totally robust, but from my test it seems ok.)
The input parameter data needs to be either a URL or ArrayBuffer type data. I used the ReadAsArrayBuffer(file) function in FileReader API to get the data.
Note: According to some other user, the library has updated and caused the code to break. According to the comment by async5 below, you need to replace textContent.bidiTexts with textContent.items.
function Pdf2TextClass(){
var self = this;
this.complete = 0;
/**
*
* #param data ArrayBuffer of the pdf file content
* #param callbackPageDone To inform the progress each time
* when a page is finished. The callback function's input parameters are:
* 1) number of pages done;
* 2) total number of pages in file.
* #param callbackAllDone The input parameter of callback function is
* the result of extracted text from pdf file.
*
*/
this.pdfToText = function(data, callbackPageDone, callbackAllDone){
console.assert( data instanceof ArrayBuffer || typeof data == 'string' );
PDFJS.getDocument( data ).then( function(pdf) {
var div = document.getElementById('viewer');
var total = pdf.numPages;
callbackPageDone( 0, total );
var layers = {};
for (i = 1; i <= total; i++){
pdf.getPage(i).then( function(page){
var n = page.pageNumber;
page.getTextContent().then( function(textContent){
if( null != textContent.bidiTexts ){
var page_text = "";
var last_block = null;
for( var k = 0; k < textContent.bidiTexts.length; k++ ){
var block = textContent.bidiTexts[k];
if( last_block != null && last_block.str[last_block.str.length-1] != ' '){
if( block.x < last_block.x )
page_text += "\r\n";
else if ( last_block.y != block.y && ( last_block.str.match(/^(\s?[a-zA-Z])$|^(.+\s[a-zA-Z])$/) == null ))
page_text += ' ';
}
page_text += block.str;
last_block = block;
}
textContent != null && console.log("page " + n + " finished."); //" content: \n" + page_text);
layers[n] = page_text + "\n\n";
}
++ self.complete;
callbackPageDone( self.complete, total );
if (self.complete == total){
window.setTimeout(function(){
var full_text = "";
var num_pages = Object.keys(layers).length;
for( var j = 1; j <= num_pages; j++)
full_text += layers[j] ;
callbackAllDone(full_text);
}, 1000);
}
}); // end of page.getTextContent().then
}); // end of page.then
} // of for
});
}; // end of pdfToText()
}; // end of class
I couldn't get gm2008's example to work (the internal data structure on pdf.js has changed apparently), so I wrote my own fully promise-based solution that doesn't use any DOM elements, queryselectors or canvas, using the updated pdf.js from the example at mozilla
It eats a file path for the upload since i'm using it with node-webkit.
You need to make sure you have the cmaps downloaded and pointed somewhere and you nee pdf.js and pdf.worker.js to get this working.
/**
* Extract text from PDFs with PDF.js
* Uses the demo pdf.js from https://mozilla.github.io/pdf.js/getting_started/
*/
this.pdfToText = function(data) {
PDFJS.workerSrc = 'js/vendor/pdf.worker.js';
PDFJS.cMapUrl = 'js/vendor/pdfjs/cmaps/';
PDFJS.cMapPacked = true;
return PDFJS.getDocument(data).then(function(pdf) {
var pages = [];
for (var i = 0; i < pdf.numPages; i++) {
pages.push(i);
}
return Promise.all(pages.map(function(pageNumber) {
return pdf.getPage(pageNumber + 1).then(function(page) {
return page.getTextContent().then(function(textContent) {
return textContent.items.map(function(item) {
return item.str;
}).join(' ');
});
});
})).then(function(pages) {
return pages.join("\r\n");
});
});
}
usage:
self.pdfToText(files[0].path).then(function(result) {
console.log("PDF done!", result);
})
Just leaving here a full working sample.
<html>
<head>
<script src="https://npmcdn.com/pdfjs-dist/build/pdf.js"></script>
</head>
<body>
<input id="pdffile" name="pdffile" type="file" />
<button id="btn" onclick="convert()">Process</button>
<div id="result"></div>
</body>
</html>
<script>
function convert() {
var fr=new FileReader();
var pdff = new Pdf2TextClass();
fr.onload=function(){
pdff.pdfToText(fr.result, null, (text) => { document.getElementById('result').innerText += text; });
}
fr.readAsDataURL(document.getElementById('pdffile').files[0])
}
function Pdf2TextClass() {
var self = this;
this.complete = 0;
this.pdfToText = function (data, callbackPageDone, callbackAllDone) {
console.assert(data instanceof ArrayBuffer || typeof data == 'string');
var loadingTask = pdfjsLib.getDocument(data);
loadingTask.promise.then(function (pdf) {
var total = pdf._pdfInfo.numPages;
//callbackPageDone( 0, total );
var layers = {};
for (i = 1; i <= total; i++) {
pdf.getPage(i).then(function (page) {
var n = page.pageNumber;
page.getTextContent().then(function (textContent) {
//console.log(textContent.items[0]);0
if (null != textContent.items) {
var page_text = "";
var last_block = null;
for (var k = 0; k < textContent.items.length; k++) {
var block = textContent.items[k];
if (last_block != null && last_block.str[last_block.str.length - 1] != ' ') {
if (block.x < last_block.x)
page_text += "\r\n";
else if (last_block.y != block.y && (last_block.str.match(/^(\s?[a-zA-Z])$|^(.+\s[a-zA-Z])$/) == null))
page_text += ' ';
}
page_text += block.str;
last_block = block;
}
textContent != null && console.log("page " + n + " finished."); //" content: \n" + page_text);
layers[n] = page_text + "\n\n";
}
++self.complete;
//callbackPageDone( self.complete, total );
if (self.complete == total) {
window.setTimeout(function () {
var full_text = "";
var num_pages = Object.keys(layers).length;
for (var j = 1; j <= num_pages; j++)
full_text += layers[j];
callbackAllDone(full_text);
}, 1000);
}
}); // end of page.getTextContent().then
}); // end of page.then
} // of for
});
}; // end of pdfToText()
}; // end of class
</script>
Here's some JavaScript code that does what you want using Pdf.js from http://hublog.hubmed.org/archives/001948.html:
var input = document.getElementById("input");
var processor = document.getElementById("processor");
var output = document.getElementById("output");
// listen for messages from the processor
window.addEventListener("message", function(event){
if (event.source != processor.contentWindow) return;
switch (event.data){
// "ready" = the processor is ready, so fetch the PDF file
case "ready":
var xhr = new XMLHttpRequest;
xhr.open('GET', input.getAttribute("src"), true);
xhr.responseType = "arraybuffer";
xhr.onload = function(event) {
processor.contentWindow.postMessage(this.response, "*");
};
xhr.send();
break;
// anything else = the processor has returned the text of the PDF
default:
output.textContent = event.data.replace(/\s+/g, " ");
break;
}
}, true);
...and here's an example:
http://git.macropus.org/2011/11/pdftotext/example/
Note: This code assumes you're using nodejs. That means you're parsing a local file instead of one from a web page since the original question doesn't explicitly ask about parsing pdfs on a web page.
#gm2008's answer was a great starting point (please read it and its comments for more info), but needed some updates (08/19) and had some unused code. I also like examples that are more full. There's more refactoring and tweaking that could be done (e.g. with await), but for now it's as close to that original answer as it could be.
As before, this uses Mozilla's PDFjs library. The npmjs package is at https://www.npmjs.com/package/pdfjs-dist.
In my experience, this doesn't do well in finding where to put spaces, but that's a problem for another time.
[Edit: I believe the update to the use of .transform has restored the whitespace as it originally behaved.]
// This file is called myPDFfileToText.js and is in the root folder
let PDFJS = require('pdfjs-dist');
let pathToPDF = 'path/to/myPDFfileToText.pdf';
let toText = Pdf2TextObj();
let onPageDone = function() {}; // don't want to do anything between pages
let onFinish = function(fullText) { console.log(fullText) };
toText.pdfToText(pathToPDF, onPageDone, onFinish);
function Pdf2TextObj() {
let self = this;
this.complete = 0;
/**
*
* #param path Path to the pdf file.
* #param callbackPageDone To inform the progress each time
* when a page is finished. The callback function's input parameters are:
* 1) number of pages done.
* 2) total number of pages in file.
* 3) the `page` object itself or null.
* #param callbackAllDone Called after all text has been collected. Input parameters:
* 1) full text of parsed pdf.
*
*/
this.pdfToText = function(path, callbackPageDone, callbackAllDone) {
// console.assert(typeof path == 'string');
PDFJS.getDocument(path).promise.then(function(pdf) {
let total = pdf.numPages;
callbackPageDone(0, total, null);
let pages = {};
// For some (pdf?) reason these don't all come in consecutive
// order. That's why they're stored as an object and then
// processed one final time at the end.
for (let pagei = 1; pagei <= total; pagei++) {
pdf.getPage(pagei).then(function(page) {
let pageNumber = page.pageNumber;
page.getTextContent().then(function(textContent) {
if (null != textContent.items) {
let page_text = "";
let last_item = null;
for (let itemsi = 0; itemsi < textContent.items.length; itemsi++) {
let item = textContent.items[itemsi];
// I think to add whitespace properly would be more complex and
// would require two loops.
if (last_item != null && last_item.str[last_item.str.length - 1] != ' ') {
let itemX = item.transform[5]
let lastItemX = last_item.transform[5]
let itemY = item.transform[4]
let lastItemY = last_item.transform[4]
if (itemX < lastItemX)
page_text += "\r\n";
else if (itemY != lastItemY && (last_item.str.match(/^(\s?[a-zA-Z])$|^(.+\s[a-zA-Z])$/) == null))
page_text += ' ';
} // ends if may need to add whitespace
page_text += item.str;
last_item = item;
} // ends for every item of text
textContent != null && console.log("page " + pageNumber + " finished.") // " content: \n" + page_text);
pages[pageNumber] = page_text + "\n\n";
} // ends if has items
++self.complete;
callbackPageDone(self.complete, total, page);
// If all done, put pages in order and combine all
// text, then pass that to the callback
if (self.complete == total) {
// Using `setTimeout()` isn't a stable way of making sure
// the process has finished. Watch out for missed pages.
// A future version might do this with promises.
setTimeout(function() {
let full_text = "";
let num_pages = Object.keys(pages).length;
for (let pageNum = 1; pageNum <= num_pages; pageNum++)
full_text += pages[pageNum];
callbackAllDone(full_text);
}, 1000);
}
}); // ends page.getTextContent().then
}); // ends page.then
} // ends for every page
});
}; // Ends pdfToText()
return self;
}; // Ends object factory
Run in the terminal:
node myPDFfileToText.js
Updated 02/2021
<script src="https://npmcdn.com/pdfjs-dist/build/pdf.js"></script>
<script>
function Pdf2TextClass(){
var self = this;
this.complete = 0;
this.pdfToText = function(data, callbackPageDone, callbackAllDone){
console.assert( data instanceof ArrayBuffer || typeof data == 'string' );
var loadingTask = pdfjsLib.getDocument(data);
loadingTask.promise.then(function(pdf) {
var total = pdf._pdfInfo.numPages;
//callbackPageDone( 0, total );
var layers = {};
for (i = 1; i <= total; i++){
pdf.getPage(i).then( function(page){
var n = page.pageNumber;
page.getTextContent().then( function(textContent){
//console.log(textContent.items[0]);0
if( null != textContent.items ){
var page_text = "";
var last_block = null;
for( var k = 0; k < textContent.items.length; k++ ){
var block = textContent.items[k];
if( last_block != null && last_block.str[last_block.str.length-1] != ' '){
if( block.x < last_block.x )
page_text += "\r\n";
else if ( last_block.y != block.y && ( last_block.str.match(/^(\s?[a-zA-Z])$|^(.+\s[a-zA-Z])$/) == null ))
page_text += ' ';
}
page_text += block.str;
last_block = block;
}
textContent != null && console.log("page " + n + " finished."); //" content: \n" + page_text);
layers[n] = page_text + "\n\n";
}
++ self.complete;
//callbackPageDone( self.complete, total );
if (self.complete == total){
window.setTimeout(function(){
var full_text = "";
var num_pages = Object.keys(layers).length;
for( var j = 1; j <= num_pages; j++)
full_text += layers[j] ;
console.log(full_text);
}, 1000);
}
}); // end of page.getTextContent().then
}); // end of page.then
} // of for
});
}; // end of pdfToText()
}; // end of class
var pdff = new Pdf2TextClass();
pdff.pdfToText('PDF_URL');
</script>
For all the people who actually want to use it on a node server:
/**
* Created by velten on 25.04.16.
*/
"use strict";
let pdfUrl = "http://example.com/example.pdf";
let request = require('request');
var pdfParser = require('pdf2json');
let pdfPipe = request({url: pdfUrl, encoding:null}).pipe(pdfParser);
pdfPipe.on("pdfParser_dataError", err => console.error(err) );
pdfPipe.on("pdfParser_dataReady", pdf => {
//optionally:
//let pdf = pdfParser.getMergedTextBlocksIfNeeded();
let count1 = 0;
//get text on a particular page
for (let page of pdf.formImage.Pages) {
count1 += page.Texts.length;
}
console.log(count1);
pdfParser.destroy();
});
It is possible but:
you would have to use the server anyway, there's no way you can get content of a file on user computer without transferring it to server and back
I don't thing anyone has written such library yet
So if you have some free time you can learn pdf format and write such a library yourself, or you can just use server side library of course.

Why does this barcode detection cause Chrome or the whole Android OS to crash?

I built a barcode scanning system using Chrome's built-in BarcodeDetector. I based the work on Paul Kinlan's QR code scanner which works fine on my phone, but when I run my own code on my phone, it often causes Chrome or the whole System UI to freeze. Sometimes it gets so bad that I need to restart the phone by holding down the power button.
I have tried debugging in the Chrome developer console, but when the phone freezes, so does the developer console.
When I comment out the actual QR code detection, I can leave the page open in Chrome for 10 minutes and it just keeps running. With QR code detection running, the phone will freeze anywhere from immediately to 3 minutes later.
I put the support files (js and HTML) in a Gist - I don't think they are the issue because everything works when the barcode scanning is commented out.
My first attempt:
// Inspired by/based on on https://github.com/PaulKinlan/qrcode/blob/production/app/scripts/main.mjs
import WebCamManager from './scan/WebCamManager.js';
(function () {
'use strict';
var QRCodeCamera = function (element) {
var root = document.getElementById(element);
var cameraRoot = root.querySelector('.CameraRealtime');
var cameraManager = new WebCamManager(cameraRoot);
var cameraVideo = root.querySelector('.Camera-video');
// Offscreen canvas is supposed to help with processing speed
var cameraCanvas = new OffscreenCanvas(1,1);
var context = cameraCanvas.getContext('2d');
const detector = new BarcodeDetector({
formats: ['qr_code'],
});
cameraManager.onframeready = async function (frameData) {
cameraCanvas.width = cameraVideo.videoWidth;
cameraCanvas.height = cameraVideo.videoHeight;
context.drawImage(frameData, 0, 0, cameraVideo.videoWidth, cameraVideo.videoHeight);
if (self.onframe) {
// Comment out the line below to stop processing QR codes
await self.onframe(cameraCanvas);
}
};
var processingFrame = false;
self.onframe = async function (cameraCanvas) {
// There is a frame in the camera, what should we do with it?
if (processingFrame == false) {
processingFrame = true;
let result = await detector.detect(cameraCanvas);
processingFrame = false;
if (result === undefined || result === null || result.length === 0) {
return
};
if ('vibrate' in navigator) {
navigator.vibrate([200]);
}
cameraManager.stop();
var currentURL = new URL(window.location.href);
var newURL;
if (result[0].rawValue
&& (newURL = new URL(result[0].rawValue))
&& newURL.hostname == currentURL.hostname
&& newURL.pathname.startsWith('/pickup/qr/')
) {
window.location.href = newURL;
} else {
alert('Unsupported QR Code: ' + result[0].rawValue);
}
cameraManager.start();
}
};
};
window.addEventListener('load', function () {
var camera = new QRCodeCamera('camera');
});
})();
I also tried splitting the QR detection to a worker (worker code in Gist), but I have the same issue:
const worker = new Worker('/js/scan-worker.js');
worker.addEventListener('message', async (e) => {
if (Array.isArray(e.data)) {
var result = e.data;
if (result === undefined || result === null || result.length === 0) {
processingFrame = false;
return
};
if ('vibrate' in navigator) {
navigator.vibrate([200]);
}
var currentURL = new URL(window.location.href);
var newURL;
if (result[0].rawValue
&& (newURL = new URL(result[0].rawValue))
&& newURL.hostname == currentURL.hostname
&& newURL.pathname.startsWith('/pickup/qr/')
) {
worker.terminate();
window.location.href = newURL;
} else {
alert('Unsupported QR Code: ' + result[0].rawValue);
}
} else {
var newError = document.createElement('div');
newError.classList.add('alert', 'alert-danger');
newError.innerHTML = e.data;
errorContainer.prepend(newError);
worker.terminate();
}
processingFrame = false;
});
cameraManager.onframeready = async function (frameData) {
if (processingFrame == false) {
cameraCanvas.width = cameraVideo.videoWidth;
cameraCanvas.height = cameraVideo.videoHeight;
context.drawImage(frameData, 0, 0, cameraVideo.videoWidth, cameraVideo.videoHeight);
if (self.onframe) {
await self.onframe(cameraCanvas, context);
}
}
};
self.onframe = async function (cameraCanvas, context) {
// There is a frame in the camera, what should we do with it?
if (processingFrame == false) {
processingFrame = true;
worker.postMessage(context.getImageData(0, 0, cameraCanvas.width, cameraCanvas.height));
}
};
Not sure if this would make a difference - all the JS code is run through Laravel Mix.

how to pause and resume voice recording in android using cordova media plugin

I'm using angularjs in visual studio.using cordova media plugin startRecord() and stopRecord() is working but not able to pause and resume recording.I'm not using media capture plugin as i don't have default recorder installed.
This is my code:
var audurl = '///storage/emulated/0/New/';
audurl += 'Voice_' + '.amr';
var mediaRec;
function recordAudio() {
mediaRec = new Media(audurl, onSuccess, onError);
mediaRec.startRecord();
}
function pauseAudio() {
mediaRec = new Media(audurl, onSuccess, onError);
mediaRec.pauseRecord();
}
thanks...
On my phone the method media.resumeRecord was not available, although in this soure-code it is defined. Nevertheless you can take advantage of all other methods, like startRecord and stopRecord, to rebuild a kind of resumeRecord function, as it is done in a handler below:
var myRecordHandler = function () {
// ALL RECORDED FILES ARE SAVED IN THIS ARRAY
var recordedAudioFiles = [];
// REMEMBER POSITION WHEN PLAYING IS STOPPED
var currentPosition = {index:0,shift:0};
// PAUSE-MODE
var paused = false;
// SET A SPECIFC DIRECTORY WHERE THE FILES ARE STORED INTO
// DEFAULT: ''
this.setDirectory = function(dir) {this.dir=dir;};
// SET FILENAME
// DEFAULT: recoredFilesX
this.setFilename = function(filename) {this.filename=filename;};
// SET MIME/Type of THE FILES;
// DEFAULT: mp3
this.setFileType = function(type) {this.filetype=type;};
// GET ALL RECORED FILES
this.getAllFiles = function() {return recordedAudioFiles;};
// STOP/PAUSE RECORDED FILES
var handleRecordedFileHold = function () {
for (var r = 0; r < recordedAudioFiles.length; r++) {
var recordedAudioFile = recordedAudioFiles[r];
if(recordedAudioFile.isBeingRecorded){
if(paused)recordedAudioFile.media.pause();
else recordedAudioFile.media.stop();
continue;
}
recordedAudioFile.duration = new Date().getTime() - recordedAudioFile.startTime;
// call release to free this created file so that it could get deleted for instance
recordedAudioFile.media.stopRecord();
recordedAudioFile.media.release();
recordedAudioFile.isBeingRecorded = true;
}
}
// START RECORDING
this.startAudioRecording = function() {
paused = false;
handleRecordedFileHold();
var dir = this.dir ? this.dir : '';
var filename = this.filename ? this.filename : 'recoredFiles';
var type = this.filetype ? this.filetype : 'mp3';
var src = dir + filename + (recordedAudioFiles.length + 1) + '.' + type;
var mediaRec = new Media(src,
function () {
console.log('recordAudio():Audio Success');
},
function (err) {
console.log('recordAudio():Audio Error: ' + err.code);
});
recordedAudioFiles.push({
media: mediaRec,
startTime: new Date().getTime()
});
mediaRec.startRecord();
}
// PAUSE RECORDING
this.pauseRecoredFiles = function () {
if(recordedAudioFiles.length){
paused = true;
clearTimeout(currentPosition.timeout);
handleRecordedFileHold();
var recoredMedia = recordedAudioFiles[currentPosition.index].media;
recoredMedia.getCurrentPosition(
function (position) {
currentPosition.shift = position;
},
function (e) {
console.log("Error getting pos=" + e);
}
);
}
}
// PLAY RECORD
this.playRecordedFiles = function () {
handleRecordedFileHold();
var playNextFile = function () {
var recoredMedia = recordedAudioFiles[currentPosition.index];
if (recoredMedia) {
if(paused){
recoredMedia.media.seekTo(currentPosition.shift*1000);
paused = false;
}
recoredMedia.media.play();
currentPosition.timeout = setTimeout(function () {
currentPosition.index++;
recoredMedia.media.stop();
playNextFile();
}, recoredMedia.duration ? recoredMedia.duration : 0);
}
else{
paused = false;
currentPosition.index = currentPosition.shift = 0;
}
};
playNextFile();
}
// RESET PLAY
this.stopRecordedFiles = function () {
currentPosition.index = currentPosition.shift = 0;
clearTimeout(currentPosition.timeout);
handleRecordedFileHold();
}
// REMOVE ALL RECORDED FILES
this.removeRecordedFiles = function() {
paused = false;
currentPosition.index = currentPosition.shift = 0;
clearTimeout(currentPosition.timeout);
handleRecordedFileHold();
recordedAudioFiles = [];
}
};
var handler = new myRecordHandler();
// you can use this handler in your functions like this:
function recordAudio() {
// records one track and stops former track if there is one
handler.startAudioRecording();
}
function playAudio() {
handler.playRecordedFiles();
}
function pauseAudio() {
handler.pauseRecoredFiles();
}
function resumeAudio() {
pauseAudio();
recordAudio();
}
function stopAudio() {
handler.stopRecordedFiles();
}
Although I could not test your directory/filenames, because I do not have this directory created on my phone, these methods might help you to to store your files in a specific directory as well as with certain filenames:
handler.setDirectory('__YOUR_DIR__');
handler.setFilename('__YOUR_FILENAME__');
handler.setFileType('__YOUR_FILETYPE__');

How to log contents of HTML5 drag and drop file that is 60MB+ without hanging for minutes?

I have a file that i want to drop on a page and read file contents. its a CSV with 9 columns. My drop command outputs file contents like this:
function drop(ev) {
ev.preventDefault();
var data = ev.dataTransfer.files[0];
var fileReader = new FileReader();
fileReader.onload = function (e) {
console.log(fileReader.result)
};
fileReader.onerror = function (e) {
throw 'Error reading CSV file';
};
// Start reading file
fileReader.readAsText(data);
return false;
}
When I drag and drop a simple file that is a couple kilobytes or 1MB, I can see the output of the contents of the file. However given a large CSV file, it takes many many minutes before it shows up. Is there a way to make it so that there is some streaming maybe where it does not look like its hanging?
With Screw-FileReader
You can get a ReadableStream and do it in a streaming fashion
'use strict'
var blob = new Blob(['111,222,333\naaa,bbb,ccc']) // simulate a file
var stream = blob.stream()
var reader = stream.getReader()
var headerString = ''
var forEachLine = function(row) {
var colums = row.split(',')
// append to DOM
console.log(colums)
}
var pump = function() {
return reader.read().then(function(result) {
var value = result.value
var done = result.done
if (done) {
// Do the last line
headerString && forEachLine(headerString)
return
}
for (var i = 0; i < value.length; i++) {
// Get the character for the current iteration
var char = String.fromCharCode(value[i])
// Check if the char is a new line
if (char.match(/[^\r\n]+/g) !== null) {
// Not a new line so lets append it to
// our header string and keep processing
headerString += char
} else {
// We found a new line character
forEachLine(headerString)
headerString = ''
}
}
return pump()
})
}
pump().then(function() {
console.log('done reading the csv')
})
<script src="https://cdn.rawgit.com/jimmywarting/Screw-FileReader/master/index.js"></script>
If you prefer using the old FileReader without dependencies, pipe's and transform
'use strict'
var blob = new Blob(['111,222,333\naaa,bbb,ccc']) // simulate a file
var fr = new FileReader()
var headerString = ''
var position = 0
var forEachLine = function forEachLine(row) {
var colums = row.split(',')
// append to DOM
console.log(colums)
}
var pump = function pump() {
return new Promise(function(resolve) {
var chunk = blob.slice(position, position + 524288)
position += 524288
fr.onload = function() {
var value = fr.result
var done = position >= blob.size
for (var i = 0; i < value.length; i++) {
var char = value[i]
// Check if the char is a new line
if (char.match(/[^\r\n]+/g) !== null) {
// Not a new line so lets append it to
// our header string and keep processing
headerString += char
} else {
// We found a new line character
forEachLine(headerString)
headerString = ''
}
}
if (done) {
// Send the last line
forEachLine(headerString)
return resolve() // done
}
return resolve(pump())
}
// Read the next chunk
fr.readAsText(chunk)
})
}
pump().then(function() {
console.log('done reading the csv')
})

How to control bandwidth in WebRTC video call?

I am trying to develop a Video Calling/Conferencing application using WebRTC and node.js.
Right now there is no facility to control bandwidth during during video call. Is there any way to control/reduce bandwidth.
(like I want make whole my web application to work on 150 kbps while video conferencing).
Any suggestions are highly appreciated.
Thanks in advance.
Try this demo. You can inject bandwidth attributes (b=AS) in the session descriptions:
audioBandwidth = 50;
videoBandwidth = 256;
function setBandwidth(sdp) {
sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + audioBandwidth + '\r\n');
sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + videoBandwidth + '\r\n');
return sdp;
}
// ----------------------------------------------------------
peer.createOffer(function (sessionDescription) {
sessionDescription.sdp = setBandwidth(sessionDescription.sdp);
peer.setLocalDescription(sessionDescription);
}, null, constraints);
peer.createAnswer(function (sessionDescription) {
sessionDescription.sdp = setBandwidth(sessionDescription.sdp);
peer.setLocalDescription(sessionDescription);
}, null, constraints);
b=AS is already present in sdp for data m-line; its default value is 50.
Updated at Sept 23, 2015
Here is a library that provides full control over both audio/video tracks' bitrates:
// here is how to use it
var bandwidth = {
screen: 300, // 300kbits minimum
audio: 50, // 50kbits minimum
video: 256 // 256kbits (both min-max)
};
var isScreenSharing = false;
sdp = BandwidthHandler.setApplicationSpecificBandwidth(sdp, bandwidth, isScreenSharing);
sdp = BandwidthHandler.setVideoBitrates(sdp, {
min: bandwidth.video,
max: bandwidth.video
});
sdp = BandwidthHandler.setOpusAttributes(sdp);
Here is the library code. Its quite big but it works!
// BandwidthHandler.js
var BandwidthHandler = (function() {
function setBAS(sdp, bandwidth, isScreen) {
if (!!navigator.mozGetUserMedia || !bandwidth) {
return sdp;
}
if (isScreen) {
if (!bandwidth.screen) {
console.warn('It seems that you are not using bandwidth for screen. Screen sharing is expected to fail.');
} else if (bandwidth.screen < 300) {
console.warn('It seems that you are using wrong bandwidth value for screen. Screen sharing is expected to fail.');
}
}
// if screen; must use at least 300kbs
if (bandwidth.screen && isScreen) {
sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + bandwidth.screen + '\r\n');
}
// remove existing bandwidth lines
if (bandwidth.audio || bandwidth.video || bandwidth.data) {
sdp = sdp.replace(/b=AS([^\r\n]+\r\n)/g, '');
}
if (bandwidth.audio) {
sdp = sdp.replace(/a=mid:audio\r\n/g, 'a=mid:audio\r\nb=AS:' + bandwidth.audio + '\r\n');
}
if (bandwidth.video) {
sdp = sdp.replace(/a=mid:video\r\n/g, 'a=mid:video\r\nb=AS:' + (isScreen ? bandwidth.screen : bandwidth.video) + '\r\n');
}
return sdp;
}
// Find the line in sdpLines that starts with |prefix|, and, if specified,
// contains |substr| (case-insensitive search).
function findLine(sdpLines, prefix, substr) {
return findLineInRange(sdpLines, 0, -1, prefix, substr);
}
// Find the line in sdpLines[startLine...endLine - 1] that starts with |prefix|
// and, if specified, contains |substr| (case-insensitive search).
function findLineInRange(sdpLines, startLine, endLine, prefix, substr) {
var realEndLine = endLine !== -1 ? endLine : sdpLines.length;
for (var i = startLine; i < realEndLine; ++i) {
if (sdpLines[i].indexOf(prefix) === 0) {
if (!substr ||
sdpLines[i].toLowerCase().indexOf(substr.toLowerCase()) !== -1) {
return i;
}
}
}
return null;
}
// Gets the codec payload type from an a=rtpmap:X line.
function getCodecPayloadType(sdpLine) {
var pattern = new RegExp('a=rtpmap:(\\d+) \\w+\\/\\d+');
var result = sdpLine.match(pattern);
return (result && result.length === 2) ? result[1] : null;
}
function setVideoBitrates(sdp, params) {
params = params || {};
var xgoogle_min_bitrate = params.min;
var xgoogle_max_bitrate = params.max;
var sdpLines = sdp.split('\r\n');
// VP8
var vp8Index = findLine(sdpLines, 'a=rtpmap', 'VP8/90000');
var vp8Payload;
if (vp8Index) {
vp8Payload = getCodecPayloadType(sdpLines[vp8Index]);
}
if (!vp8Payload) {
return sdp;
}
var rtxIndex = findLine(sdpLines, 'a=rtpmap', 'rtx/90000');
var rtxPayload;
if (rtxIndex) {
rtxPayload = getCodecPayloadType(sdpLines[rtxIndex]);
}
if (!rtxIndex) {
return sdp;
}
var rtxFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + rtxPayload.toString());
if (rtxFmtpLineIndex !== null) {
var appendrtxNext = '\r\n';
appendrtxNext += 'a=fmtp:' + vp8Payload + ' x-google-min-bitrate=' + (xgoogle_min_bitrate || '228') + '; x-google-max-bitrate=' + (xgoogle_max_bitrate || '228');
sdpLines[rtxFmtpLineIndex] = sdpLines[rtxFmtpLineIndex].concat(appendrtxNext);
sdp = sdpLines.join('\r\n');
}
return sdp;
}
function setOpusAttributes(sdp, params) {
params = params || {};
var sdpLines = sdp.split('\r\n');
// Opus
var opusIndex = findLine(sdpLines, 'a=rtpmap', 'opus/48000');
var opusPayload;
if (opusIndex) {
opusPayload = getCodecPayloadType(sdpLines[opusIndex]);
}
if (!opusPayload) {
return sdp;
}
var opusFmtpLineIndex = findLine(sdpLines, 'a=fmtp:' + opusPayload.toString());
if (opusFmtpLineIndex === null) {
return sdp;
}
var appendOpusNext = '';
appendOpusNext += '; stereo=' + (typeof params.stereo != 'undefined' ? params.stereo : '1');
appendOpusNext += '; sprop-stereo=' + (typeof params['sprop-stereo'] != 'undefined' ? params['sprop-stereo'] : '1');
if (typeof params.maxaveragebitrate != 'undefined') {
appendOpusNext += '; maxaveragebitrate=' + (params.maxaveragebitrate || 128 * 1024 * 8);
}
if (typeof params.maxplaybackrate != 'undefined') {
appendOpusNext += '; maxplaybackrate=' + (params.maxplaybackrate || 128 * 1024 * 8);
}
if (typeof params.cbr != 'undefined') {
appendOpusNext += '; cbr=' + (typeof params.cbr != 'undefined' ? params.cbr : '1');
}
if (typeof params.useinbandfec != 'undefined') {
appendOpusNext += '; useinbandfec=' + params.useinbandfec;
}
if (typeof params.usedtx != 'undefined') {
appendOpusNext += '; usedtx=' + params.usedtx;
}
if (typeof params.maxptime != 'undefined') {
appendOpusNext += '\r\na=maxptime:' + params.maxptime;
}
sdpLines[opusFmtpLineIndex] = sdpLines[opusFmtpLineIndex].concat(appendOpusNext);
sdp = sdpLines.join('\r\n');
return sdp;
}
return {
setApplicationSpecificBandwidth: function(sdp, bandwidth, isScreen) {
return setBAS(sdp, bandwidth, isScreen);
},
setVideoBitrates: function(sdp, params) {
return setVideoBitrates(sdp, params);
},
setOpusAttributes: function(sdp, params) {
return setOpusAttributes(sdp, params);
}
};
})();
Here is how to set advance opus bitrate parameters:
sdp = BandwidthHandler.setOpusAttributes(sdp, {
'stereo': 0, // to disable stereo (to force mono audio)
'sprop-stereo': 1,
'maxaveragebitrate': 500 * 1024 * 8, // 500 kbits
'maxplaybackrate': 500 * 1024 * 8, // 500 kbits
'cbr': 0, // disable cbr
'useinbandfec': 1, // use inband fec
'usedtx': 1, // use dtx
'maxptime': 3
});
A more up-to-date answer
const videobitrate = 20000;
var offer = pc.localDescription;
// Set bandwidth for video
offer.sdp = offer.sdp.replace(/(m=video.*\r\n)/g, `$1b=AS:${videobitrate}\r\n`);
pc.setLocalDescription(offer);
Explanation: a=mid:video is not a guaranteed tag. For receive only video, you might not see it or see a=mid:0. Generally it's a better bet to look for the m=video xxxx xxxx (or similar audio) tag and append the bandwidth parameters underneath
Not sure if this helps, but you can limit the video resolution from getUserMedia with constraints: see demo at simpl.info/getusermedia/constraints/.
My answer is not for node.js, but maybe someone stuck with controlling webrtc bandwidth while developing a native phone app (iOS, android).
So, at least in version GoogleWebRTC (1.1.31999) for iOS and org.webrtc:google-webrtc:1.0.22672 for android there is method in PeerConnection instance.
For iOS:
let updateBitrateSuccessful = pc.setBweMinBitrateBps(300000, currentBitrateBps: 1000000, maxBitrateBps: 3000000)
print("Update rtc connection bitrate " + (updateBitrateSuccessful ? "successful" : "failed"))
Respectively, for Android:
boolean updateBitrateSuccessful = pc.setBitrate(300000, 1000000, 3000000);
Log.d("AppLog", "Update rtc connection bitrate " + (updateBitrateSuccessful ? "successful" : "failed"));
It depends on what SFU media server you're using. But in short, your media server needs to tell the client browser what maximum bitrate it should send, by setting the bandwidth attribute in the answer SDP, as well as in the REMB message it periodically sends.
The REMB (receiver estimated maximum bitrate) applies separately to audio and video streams (at least on desktop Chrome and Firefox that I tested). So if REMB is set to 75kps and you have one audio and one video stream, then each will confine to 75kps for a total transport bitrate of 150kps. You should use chrome://webrtc-internals to test and verify this.
If you are using OPUS as the audio codec, you can control the audio bandwidth separately by setting the maxaveragebitrate attribute in the answer SDP. Setting this attribute will override the REMB value (verified on Chrome). So you can set audio bitrate to 16kps and the video bitrate (via REMB) to 134kps for a combined transport bitrate of 150.
Note that the REMB is sent by your server, so your server needs to support this. The other SDP attributes can be manipulated on the client side by modifying the answer SDP that you receive, right before passing it to setRemoteDescription().
This is my limited understanding based on online research. It's not based on deep knowledge of the technology stack, so please take it with a grain of salt.
You should also be able to use bandwidth constraints on the stream (see this demo), but it doesn't appear to be working, even in the latest canary (29.0.1529.3).
There's some discussion of the SDP-based approach on the discuss-webrtc mailing list, which links to WebRTC bug 1846.
I did it Yesterday and it works like a charm! in my case, it was needed to prevent slow and old phones get freeze during a videocall! have a look
function handle_offer_sdp(offer) {
let sdp = offer.sdp.split('\r\n');//convert to an concatenable array
let new_sdp = '';
let position = null;
sdp = sdp.slice(0, -1); //remove the last comma ','
for(let i = 0; i < sdp.length; i++) {//look if exists already a b=AS:XXX line
if(sdp[i].match(/b=AS:/)) {
position = i; //mark the position
}
}
if(position) {
sdp.splice(position, 1);//remove if exists
}
for(let i = 0; i < sdp.length; i++) {
if(sdp[i].match(/m=video/)) {//modify and add the new lines for video
new_sdp += sdp[i] + '\r\n' + 'b=AS:' + '128' + '\r\n';
}
else {
if(sdp[i].match(/m=audio/)) { //modify and add the new lines for audio
new_sdp += sdp[i] + '\r\n' + 'b=AS:' + 64 + '\r\n';
}
else {
new_sdp += sdp[i] + '\r\n';
}
}
}
return new_sdp; //return the new sdp
}
pc.createOffer(function(offer) {
offer.sdp = handle_offer_sdp(offer); //invoke function saving the new sdp
pc.setLocalDescription(offer);
}, function(error) {
console.log('error -> ' + error);
});
I recommend to change value of maxBitrate property as described here https://stackoverflow.com/a/71223675/1199820
Check this, this works for me.
Control your bitrate via getSenders(), after peer is connected then you can set your maximum bitrate.
This method allow you to control bitrate without renegotiation. so,
you can change the streaming quality during a call
//bandwidth => "unlimited", 75 kbps, 250 kbps, 1000 kbps, 2000 kbps
var bandwidth = 75;
const sender = pc1.getSenders()[0];
const parameters = sender.getParameters();
if (!parameters.encodings) {
parameters.encodings = [{}];
}
if (bandwidth === 'unlimited') {
delete parameters.encodings[0].maxBitrate;
} else {
parameters.encodings[0].maxBitrate = bandwidth * 1000;
}
sender.setParameters(parameters)
.then(() => {
// on success
})
.catch(e => console.error(e));
Reference code & demo

Categories

Resources