Download speed using php [duplicate] - javascript
How can I create a JavaScript page that will detect the user’s internet speed and show it on the page? Something like “your internet speed is ??/?? Kb/s”.
It's possible to some extent but won't be really accurate, the idea is load image with a known file size then in its onload event measure how much time passed until that event was triggered, and divide this time in the image file size.
Example can be found here: Calculate speed using javascript
Test case applying the fix suggested there:
//JUST AN EXAMPLE, PLEASE USE YOUR OWN PICTURE!
var imageAddr = "http://www.kenrockwell.com/contax/images/g2/examples/31120037-5mb.jpg";
var downloadSize = 4995374; //bytes
function ShowProgressMessage(msg) {
if (console) {
if (typeof msg == "string") {
console.log(msg);
} else {
for (var i = 0; i < msg.length; i++) {
console.log(msg[i]);
}
}
}
var oProgress = document.getElementById("progress");
if (oProgress) {
var actualHTML = (typeof msg == "string") ? msg : msg.join("<br />");
oProgress.innerHTML = actualHTML;
}
}
function InitiateSpeedDetection() {
ShowProgressMessage("Loading the image, please wait...");
window.setTimeout(MeasureConnectionSpeed, 1);
};
if (window.addEventListener) {
window.addEventListener('load', InitiateSpeedDetection, false);
} else if (window.attachEvent) {
window.attachEvent('onload', InitiateSpeedDetection);
}
function MeasureConnectionSpeed() {
var startTime, endTime;
var download = new Image();
download.onload = function () {
endTime = (new Date()).getTime();
showResults();
}
download.onerror = function (err, msg) {
ShowProgressMessage("Invalid image, or error downloading");
}
startTime = (new Date()).getTime();
var cacheBuster = "?nnn=" + startTime;
download.src = imageAddr + cacheBuster;
function showResults() {
var duration = (endTime - startTime) / 1000;
var bitsLoaded = downloadSize * 8;
var speedBps = (bitsLoaded / duration).toFixed(2);
var speedKbps = (speedBps / 1024).toFixed(2);
var speedMbps = (speedKbps / 1024).toFixed(2);
ShowProgressMessage([
"Your connection speed is:",
speedBps + " bps",
speedKbps + " kbps",
speedMbps + " Mbps"
]);
}
}
<h1 id="progress">JavaScript is turned off, or your browser is realllllly slow</h1>
Quick comparison with "real" speed test service showed small difference of 0.12 Mbps when using big picture.
To ensure the integrity of the test, you can run the code with Chrome dev tool throttling enabled and then see if the result matches the limitation. (credit goes to user284130 :))
Important things to keep in mind:
The image being used should be properly optimized and compressed. If it isn't, then default compression on connections by the web server might show speed bigger than it actually is. Another option is using uncompressible file format, e.g. jpg. (thanks Rauli Rajande for pointing this out and Fluxine for reminding me)
The cache buster mechanism described above might not work with some CDN servers, which can be configured to ignore query string parameters, hence better setting cache control headers on the image itself. (thanks orcaman for pointing this out))
The bigger the image size is, the better. Larger image will make the test more accurate, 5 mb is decent, but if you can use even a bigger one it would be better.
Well, this is 2017 so you now have Network Information API (albeit with a limited support across browsers as of now) to get some sort of estimate downlink speed information:
navigator.connection.downlink
This is effective bandwidth estimate in Mbits per sec. The browser makes this estimate from recently observed application layer throughput across recently active connections. Needless to say, the biggest advantage of this approach is that you need not download any content just for bandwidth/ speed calculation.
You can look at this and a couple of other related attributes here
Due to it's limited support and different implementations across browsers (as of Nov 2017), would strongly recommend read this in detail
I needed a quick way to determine if the user connection speed was fast enough to enable/disable some features in a site I’m working on, I made this little script that averages the time it takes to download a single (small) image a number of times, it's working pretty accurately in my tests, being able to clearly distinguish between 3G or Wi-Fi for example, maybe someone can make a more elegant version or even a jQuery plugin.
var arrTimes = [];
var i = 0; // start
var timesToTest = 5;
var tThreshold = 150; //ms
var testImage = "http://www.google.com/images/phd/px.gif"; // small image in your server
var dummyImage = new Image();
var isConnectedFast = false;
testLatency(function(avg){
isConnectedFast = (avg <= tThreshold);
/** output */
document.body.appendChild(
document.createTextNode("Time: " + (avg.toFixed(2)) + "ms - isConnectedFast? " + isConnectedFast)
);
});
/** test and average time took to download image from server, called recursively timesToTest times */
function testLatency(cb) {
var tStart = new Date().getTime();
if (i<timesToTest-1) {
dummyImage.src = testImage + '?t=' + tStart;
dummyImage.onload = function() {
var tEnd = new Date().getTime();
var tTimeTook = tEnd-tStart;
arrTimes[i] = tTimeTook;
testLatency(cb);
i++;
};
} else {
/** calculate average of array items then callback */
var sum = arrTimes.reduce(function(a, b) { return a + b; });
var avg = sum / arrTimes.length;
cb(avg);
}
}
As I outline in this other answer here on StackOverflow, you can do this by timing the download of files of various sizes (start small, ramp up if the connection seems to allow it), ensuring through cache headers and such that the file is really being read from the remote server and not being retrieved from cache. This doesn't necessarily require that you have a server of your own (the files could be coming from S3 or similar), but you will need somewhere to get the files from in order to test connection speed.
That said, point-in-time bandwidth tests are notoriously unreliable, being as they are impacted by other items being downloaded in other windows, the speed of your server, links en route, etc., etc. But you can get a rough idea using this sort of technique.
Even though this is old and answered, i´d like to share the solution i made out of it 2020 base on Shadow Wizard Says No More War´s solution
I just merged it into an object that comes with the flexibility to run at anytime and run a callbacks if the specified mbps is higher or lower the measurement result.
you can start the test anywhere after you included the testConnectionSpeed Object by running the
/**
* #param float mbps - Specify a limit of mbps.
* #param function more(float result) - Called if more mbps than specified limit.
* #param function less(float result) - Called if less mbps than specified limit.
*/
testConnectionSpeed.run(mbps, more, less)
for example:
var testConnectionSpeed = {
imageAddr : "https://upload.wikimedia.org/wikipedia/commons/a/a6/Brandenburger_Tor_abends.jpg", // this is just an example, you rather want an image hosted on your server
downloadSize : 2707459, // Must match the file above (from your server ideally)
run:function(mbps_max,cb_gt,cb_lt){
testConnectionSpeed.mbps_max = parseFloat(mbps_max) ? parseFloat(mbps_max) : 0;
testConnectionSpeed.cb_gt = cb_gt;
testConnectionSpeed.cb_lt = cb_lt;
testConnectionSpeed.InitiateSpeedDetection();
},
InitiateSpeedDetection: function() {
window.setTimeout(testConnectionSpeed.MeasureConnectionSpeed, 1);
},
result:function(){
var duration = (endTime - startTime) / 1000;
var bitsLoaded = testConnectionSpeed.downloadSize * 8;
var speedBps = (bitsLoaded / duration).toFixed(2);
var speedKbps = (speedBps / 1024).toFixed(2);
var speedMbps = (speedKbps / 1024).toFixed(2);
if(speedMbps >= (testConnectionSpeed.max_mbps ? testConnectionSpeed.max_mbps : 1) ){
testConnectionSpeed.cb_gt ? testConnectionSpeed.cb_gt(speedMbps) : false;
}else {
testConnectionSpeed.cb_lt ? testConnectionSpeed.cb_lt(speedMbps) : false;
}
},
MeasureConnectionSpeed:function() {
var download = new Image();
download.onload = function () {
endTime = (new Date()).getTime();
testConnectionSpeed.result();
}
startTime = (new Date()).getTime();
var cacheBuster = "?nnn=" + startTime;
download.src = testConnectionSpeed.imageAddr + cacheBuster;
}
}
// start test immediatly, you could also call this on any event or whenever you want
testConnectionSpeed.run(1.5, function(mbps){console.log(">= 1.5Mbps ("+mbps+"Mbps)")}, function(mbps){console.log("< 1.5Mbps("+mbps+"Mbps)")} )
I used this successfuly to load lowres media for slow internet connections. You have to play around a bit because on the one hand, the larger the image, the more reasonable the test, on the other hand the test will take way much longer for slow connection and in my case I especially did not want slow connection users to load lots of MBs.
The image trick is cool but in my tests it was loading before some ajax calls I wanted to be complete.
The proper solution in 2017 is to use a worker (http://caniuse.com/#feat=webworkers).
The worker will look like:
/**
* This function performs a synchronous request
* and returns an object contain informations about the download
* time and size
*/
function measure(filename) {
var xhr = new XMLHttpRequest();
var measure = {};
xhr.open("GET", filename + '?' + (new Date()).getTime(), false);
measure.start = (new Date()).getTime();
xhr.send(null);
measure.end = (new Date()).getTime();
measure.len = parseInt(xhr.getResponseHeader('Content-Length') || 0);
measure.delta = measure.end - measure.start;
return measure;
}
/**
* Requires that we pass a base url to the worker
* The worker will measure the download time needed to get
* a ~0KB and a 100KB.
* It will return a string that serializes this informations as
* pipe separated values
*/
onmessage = function(e) {
measure0 = measure(e.data.base_url + '/test/0.bz2');
measure100 = measure(e.data.base_url + '/test/100K.bz2');
postMessage(
measure0.delta + '|' +
measure0.len + '|' +
measure100.delta + '|' +
measure100.len
);
};
The js file that will invoke the Worker:
var base_url = PORTAL_URL + '/++plone++experimental.bwtools';
if (typeof(Worker) === 'undefined') {
return; // unsupported
}
w = new Worker(base_url + "/scripts/worker.js");
w.postMessage({
base_url: base_url
});
w.onmessage = function(event) {
if (event.data) {
set_cookie(event.data);
}
};
Code taken from a Plone package I wrote:
https://github.com/collective/experimental.bwtools/blob/master/src/experimental/bwtools/browser/static/scripts/
It's better to use images for testing the speed. But if you have to deal with zip files, the below code works.
var fileURL = "your/url/here/testfile.zip";
var request = new XMLHttpRequest();
var avoidCache = "?avoidcache=" + (new Date()).getTime();;
request.open('GET', fileURL + avoidCache, true);
request.responseType = "application/zip";
var startTime = (new Date()).getTime();
var endTime = startTime;
request.onreadystatechange = function () {
if (request.readyState == 2)
{
//ready state 2 is when the request is sent
startTime = (new Date().getTime());
}
if (request.readyState == 4)
{
endTime = (new Date()).getTime();
var downloadSize = request.responseText.length;
var time = (endTime - startTime) / 1000;
var sizeInBits = downloadSize * 8;
var speed = ((sizeInBits / time) / (1024 * 1024)).toFixed(2);
console.log(downloadSize, time, speed);
}
}
request.send();
This will not work very well with files < 10MB. You will have to run aggregated results on multiple download attempts.
thanks to Punit S answer, for detecting dynamic connection speed change, you can use the following code :
navigator.connection.onchange = function () {
//do what you need to do ,on speed change event
console.log('Connection Speed Changed');
}
Improving upon John Smith's answer, a nice and clean solution which returns a Promise and thus can be used with async/await. Returns a value in Mbps.
const imageAddr = 'https://upload.wikimedia.org/wikipedia/commons/a/a6/Brandenburger_Tor_abends.jpg';
const downloadSize = 2707459; // this must match with the image above
let startTime, endTime;
async function measureConnectionSpeed() {
startTime = (new Date()).getTime();
const cacheBuster = '?nnn=' + startTime;
const download = new Image();
download.src = imageAddr + cacheBuster;
// this returns when the image is finished downloading
await download.decode();
endTime = (new Date()).getTime();
const duration = (endTime - startTime) / 1000;
const bitsLoaded = downloadSize * 8;
const speedBps = (bitsLoaded / duration).toFixed(2);
const speedKbps = (speedBps / 1024).toFixed(2);
const speedMbps = (speedKbps / 1024).toFixed(2);
return Math.round(Number(speedMbps));
}
I needed something similar, so I wrote https://github.com/beradrian/jsbandwidth. This is a rewrite of https://code.google.com/p/jsbandwidth/.
The idea is to make two calls through Ajax, one to download and the other to upload through POST.
It should work with both jQuery.ajax or Angular $http.
//JUST AN EXAMPLE, PLEASE USE YOUR OWN PICTURE!
var imageAddr = "https://i.ibb.co/sPbbkkZ/pexels-lisa-1540258.jpg";
var downloadSize = 10500000; //bytes
function ShowProgressMessage(msg) {
if (console) {
if (typeof msg == "string") {
console.log(msg);
} else {
for (var i = 0; i < msg.length; i++) {
console.log(msg[i]);
}
}
}
var oProgress = document.getElementById("progress");
if (oProgress) {
var actualHTML = (typeof msg == "string") ? msg : msg.join("<br />");
oProgress.innerHTML = actualHTML;
}
}
function InitiateSpeedDetection() {
ShowProgressMessage("Loading the image, please wait...");
window.setTimeout(MeasureConnectionSpeed, 1);
};
if (window.addEventListener) {
window.addEventListener('load', InitiateSpeedDetection, false);
} else if (window.attachEvent) {
window.attachEvent('onload', InitiateSpeedDetection);
}
function MeasureConnectionSpeed() {
var startTime, endTime;
var download = new Image();
download.onload = function () {
endTime = (new Date()).getTime();
showResults();
}
download.onerror = function (err, msg) {
ShowProgressMessage("Invalid image, or error downloading");
}
startTime = (new Date()).getTime();
var cacheBuster = "?nnn=" + startTime;
download.src = imageAddr + cacheBuster;
function showResults() {
var duration = (endTime - startTime) / 1000;
var bitsLoaded = downloadSize * 8;
var speedBps = (bitsLoaded / duration).toFixed(2);
var speedKbps = (speedBps / 1024).toFixed(2);
var speedMbps = (speedKbps / 1024).toFixed(2);
ShowProgressMessage([
"Your connection speed is:",
speedBps + " bps",
speedKbps + " kbps",
speedMbps + " Mbps"
]);
}
}
<h1 id="progress">JavaScript is turned off, or your browser is realllllly slow</h1>
Mini snippet:
var speedtest = {};
function speedTest_start(name) { speedtest[name]= +new Date(); }
function speedTest_stop(name) { return +new Date() - speedtest[name] + (delete
speedtest[name]?0:0); }
use like:
speedTest_start("test1");
// ... some code
speedTest_stop("test1");
// returns the time duration in ms
Also more tests possible:
speedTest_start("whole");
// ... some code
speedTest_start("part");
// ... some code
speedTest_stop("part");
// returns the time duration in ms of "part"
// ... some code
speedTest_stop("whole");
// returns the time duration in ms of "whole"
Related
How can i input a javascript object's value into a sql database?
I'm new to programming/web development, learning it in community college, so you'll probably have to explain your answers to me as if i'm an idiot. Anyway, my current assignment in a class i'm taking is to build upon a basic app that times a staff meeting and calculates the cost said meeting based on the participants' salary per second. What i've learned, at a basic level, how to take the client's input (through text boxes) and save them in a database to be called upon and displayed later, but i can't figure out how to do that with objects in javascript. My point in doing that is to save the cost of the meeting, as well as participants, so that the client can display it later. Here is the javascript code: window.onload = function() { var startTimestamp = 0; var endTimestamp = 0; var startTimestampButton = document.getElementById('start-timestamp'); var startTimestampElement = document.getElementById('start-timestamp-value'); startTimestampButton.onclick = function() { startTimestamp = Date.now(); startTimestampElement.innerHTML = startTimestamp; } var endTimestampButton = document.getElementById('end-timestamp'); var endTimestampElement = document.getElementById('end-timestamp-value'); endTimestampButton.onclick = function() { endTimestamp = Date.now(); endTimestampElement.innerHTML = endTimestamp; } var selectElement = document.getElementById('selected-staff'); for (var username in data) { var obj = data[username]; var opt = document.createElement('option'); opt.value = obj.SAL_SECOND; opt.innerHTML = obj.FIRST_NAME + " " + obj.LAST_NAME; selectElement.appendChild(opt); } var pageHeading = document.getElementById('page-heading'); var totalSalaryPerSecondForSelectedStaff = 0; selectElement.onchange = function() { totalSalaryPerSecondForSelectedStaff = 0; for(var i=0; i < selectElement.length; i++) { if (selectElement.options[i].selected) { totalSalaryPerSecondForSelectedStaff += Number(selectElement.options[i].value); } } //pageHeading.innerHTML = totalSalaryPerSecondForSelectedStaff; }; window.setInterval(function() { if (totalSalaryPerSecondForSelectedStaff > 0 && startTimestamp > 0 && endTimestamp === 0) { var currentTimestamp = Date.now(); **var meetingCost = totalSalaryPerSecondForSelectedStaff * ((currentTimestamp - startTimestamp) / 1000);** if (meetingCost < 10) { pageHeading.innerHTML = "☺ $" + totalSalaryPerSecondForSelectedStaff * ((currentTimestamp - startTimestamp) / 1000); } else { pageHeading.innerHTML = "☹ $" + totalSalaryPerSecondForSelectedStaff * ((currentTimestamp - startTimestamp) / 1000); } console.log('Start Timestamp: ' + startTimestamp); console.log('Current Timestamp: ' + currentTimestamp); console.log('Total Salary Per Second for Selected: ' + totalSalaryPerSecondForSelectedStaff); } }, 1000); I couldn't figure out how to get to "var meetingCost" in a separate php file, or how to just input it to the database in the same file. Would really appreciate the help! This is kind of over my head right now!
Up to my knowledge it's better you to use ajax function and send the value into a php file and save it into mysql it's better and easy. or otherwise i believe that your getting the 'meetingCost' value in a input filed so you can put this input filed inside a form and on form submit you will get value in your php page and then save it into mysql
You can use ajax to send data to a separate php file. Here's an example, var xmlhttp; if(window.XMLHttpRequest){ xmlhttp=new XMLHttpRequest(); // code for IE7+, Firefox, Chrome, Opera, Safari }else{ xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); // code for IE6, IE5 } xmlhttp.onreadystatechange=function(){ if(xmlhttp.readyState==4 && xmlhttp.status==200){ // success } } xmlhttp.open("POST","process_form.php",true); xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded"); xmlhttp.send("meeting_cost="+meetingCost); And in the process_form.php file, you can catch and process the data in the following way, if(isset($_POST['meeting_cost']) && !empty($_POST['meeting_cost'])){ // do your database operations here }
Html5 & JavaScript Text to Speech conversion [duplicate]
I am getting a problem when trying to use Speech Synthesis API in Chrome 33. It works perfectly with a shorter text, but if I try longer text, it just stops in the middle. After it has stopped once like that, the Speech Synthesis does not work anywhere within Chrome until the browser is restarted. Example code (http://jsfiddle.net/Mdm47/1/): function speak(text) { var msg = new SpeechSynthesisUtterance(); var voices = speechSynthesis.getVoices(); msg.voice = voices[10]; msg.voiceURI = 'native'; msg.volume = 1; msg.rate = 1; msg.pitch = 2; msg.text = text; msg.lang = 'en-US'; speechSynthesis.speak(msg); } speak('Short text'); speak('Collaboratively administrate empowered markets via plug-and-play networks. Dynamically procrastinate B2C users after installed base benefits. Dramatically visualize customer directed convergence without revolutionary ROI. Efficiently unleash cross-media information without cross-media value. Quickly maximize timely deliverables for real-time schemas. Dramatically maintain clicks-and-mortar solutions without functional solutions.'); speak('Another short text'); It stops speaking in the middle of the second text, and I can't get any other page to speak after that. Is it a browser bug or some kind of security limitation?
I've had this issue for a while now with Google Chrome Speech Synthesis. After some investigation, I discovered the following: The breaking of the utterances only happens when the voice is not a native voice, The cutting out usually occurs between 200-300 characters, When it does break you can un-freeze it by doing speechSynthesis.cancel(); The 'onend' event sometimes decides not to fire. A quirky work-around to this is to console.log() out the utterance object before speaking it. Also I found wrapping the speak invocation in a setTimeout callback helps smooth these issues out. In response to these problems, I have written a function that overcomes the character limit, by chunking the text up into smaller utterances, and playing them one after another. Obviously you'll get some odd sounds sometimes as sentences might be chunked into two separate utterances with a small time delay in between each, however the code will try and split these points at punctuation marks as to make the breaks in sound less obvious. Update I've made this work-around publicly available at https://gist.github.com/woollsta/2d146f13878a301b36d7#file-chunkify-js. Many thanks to Brett Zamir for his contributions. The function: var speechUtteranceChunker = function (utt, settings, callback) { settings = settings || {}; var newUtt; var txt = (settings && settings.offset !== undefined ? utt.text.substring(settings.offset) : utt.text); if (utt.voice && utt.voice.voiceURI === 'native') { // Not part of the spec newUtt = utt; newUtt.text = txt; newUtt.addEventListener('end', function () { if (speechUtteranceChunker.cancel) { speechUtteranceChunker.cancel = false; } if (callback !== undefined) { callback(); } }); } else { var chunkLength = (settings && settings.chunkLength) || 160; var pattRegex = new RegExp('^[\\s\\S]{' + Math.floor(chunkLength / 2) + ',' + chunkLength + '}[.!?,]{1}|^[\\s\\S]{1,' + chunkLength + '}$|^[\\s\\S]{1,' + chunkLength + '} '); var chunkArr = txt.match(pattRegex); if (chunkArr[0] === undefined || chunkArr[0].length <= 2) { //call once all text has been spoken... if (callback !== undefined) { callback(); } return; } var chunk = chunkArr[0]; newUtt = new SpeechSynthesisUtterance(chunk); var x; for (x in utt) { if (utt.hasOwnProperty(x) && x !== 'text') { newUtt[x] = utt[x]; } } newUtt.addEventListener('end', function () { if (speechUtteranceChunker.cancel) { speechUtteranceChunker.cancel = false; return; } settings.offset = settings.offset || 0; settings.offset += chunk.length - 1; speechUtteranceChunker(utt, settings, callback); }); } if (settings.modifier) { settings.modifier(newUtt); } console.log(newUtt); //IMPORTANT!! Do not remove: Logging the object out fixes some onend firing issues. //placing the speak invocation inside a callback fixes ordering and onend issues. setTimeout(function () { speechSynthesis.speak(newUtt); }, 0); }; How to use it... //create an utterance as you normally would... var myLongText = "This is some long text, oh my goodness look how long I'm getting, wooooohooo!"; var utterance = new SpeechSynthesisUtterance(myLongText); //modify it as you normally would var voiceArr = speechSynthesis.getVoices(); utterance.voice = voiceArr[2]; //pass it into the chunking function to have it played out. //you can set the max number of characters by changing the chunkLength property below. //a callback function can also be added that will fire once the entire text has been spoken. speechUtteranceChunker(utterance, { chunkLength: 120 }, function () { //some code to execute when done console.log('done'); }); Hope people find this as useful.
I have solved the probleme while having a timer function which call the pause() and resume() function and callset the timer again. On the onend event I clear the timer. var myTimeout; function myTimer() { window.speechSynthesis.pause(); window.speechSynthesis.resume(); myTimeout = setTimeout(myTimer, 10000); } ... window.speechSynthesis.cancel(); myTimeout = setTimeout(myTimer, 10000); var toSpeak = "some text"; var utt = new SpeechSynthesisUtterance(toSpeak); ... utt.onend = function() { clearTimeout(myTimeout); } window.speechSynthesis.speak(utt); ... This seem to work well.
A simple and effective solution is to resume periodically. function resumeInfinity() { window.speechSynthesis.resume(); timeoutResumeInfinity = setTimeout(resumeInfinity, 1000); } You can associate this with the onend and onstart events, so you will only be invoking the resume if necessary. Something like: var utterance = new SpeechSynthesisUtterance(); utterance.onstart = function(event) { resumeInfinity(); }; utterance.onend = function(event) { clearTimeout(timeoutResumeInfinity); }; I discovered this by chance! Hope this help!
The problem with Peter's answer is it doesn't work when you have a queue of speech synthesis set up. The script will put the new chunk at the end of the queue, and thus out of order. Example: https://jsfiddle.net/1gzkja90/ <script type='text/javascript' src='http://code.jquery.com/jquery-2.1.0.js'></script> <script type='text/javascript'> u = new SpeechSynthesisUtterance(); $(document).ready(function () { $('.t').each(function () { u = new SpeechSynthesisUtterance($(this).text()); speechUtteranceChunker(u, { chunkLength: 120 }, function () { console.log('end'); }); }); }); /** * Chunkify * Google Chrome Speech Synthesis Chunking Pattern * Fixes inconsistencies with speaking long texts in speechUtterance objects * Licensed under the MIT License * * Peter Woolley and Brett Zamir */ var speechUtteranceChunker = function (utt, settings, callback) { settings = settings || {}; var newUtt; var txt = (settings && settings.offset !== undefined ? utt.text.substring(settings.offset) : utt.text); if (utt.voice && utt.voice.voiceURI === 'native') { // Not part of the spec newUtt = utt; newUtt.text = txt; newUtt.addEventListener('end', function () { if (speechUtteranceChunker.cancel) { speechUtteranceChunker.cancel = false; } if (callback !== undefined) { callback(); } }); } else { var chunkLength = (settings && settings.chunkLength) || 160; var pattRegex = new RegExp('^[\\s\\S]{' + Math.floor(chunkLength / 2) + ',' + chunkLength + '}[.!?,]{1}|^[\\s\\S]{1,' + chunkLength + '}$|^[\\s\\S]{1,' + chunkLength + '} '); var chunkArr = txt.match(pattRegex); if (chunkArr[0] === undefined || chunkArr[0].length <= 2) { //call once all text has been spoken... if (callback !== undefined) { callback(); } return; } var chunk = chunkArr[0]; newUtt = new SpeechSynthesisUtterance(chunk); var x; for (x in utt) { if (utt.hasOwnProperty(x) && x !== 'text') { newUtt[x] = utt[x]; } } newUtt.addEventListener('end', function () { if (speechUtteranceChunker.cancel) { speechUtteranceChunker.cancel = false; return; } settings.offset = settings.offset || 0; settings.offset += chunk.length - 1; speechUtteranceChunker(utt, settings, callback); }); } if (settings.modifier) { settings.modifier(newUtt); } console.log(newUtt); //IMPORTANT!! Do not remove: Logging the object out fixes some onend firing issues. //placing the speak invocation inside a callback fixes ordering and onend issues. setTimeout(function () { speechSynthesis.speak(newUtt); }, 0); }; </script> <p class="t">MLA format follows the author-page method of in-text citation. This means that the author's last name and the page number(s) from which the quotation or paraphrase is taken must appear in the text, and a complete reference should appear on your Works Cited page. The author's name may appear either in the sentence itself or in parentheses following the quotation or paraphrase, but the page number(s) should always appear in the parentheses, not in the text of your sentence.</p> <p class="t">Joe waited for the train.</p> <p class="t">The train was late.</p> <p class="t">Mary and Samantha took the bus.</p> In my case, the answer was to "chunk" the string before adding them to the queue. See here: http://jsfiddle.net/vqvyjzq4/ Many props to Peter for the idea as well as the regex (which I still have yet to conquer.) I'm sure the javascript can be cleaned up, this is more of a proof of concept. <script type='text/javascript' src='http://code.jquery.com/jquery-2.1.0.js'></script> <script type='text/javascript'> var chunkLength = 120; var pattRegex = new RegExp('^[\\s\\S]{' + Math.floor(chunkLength / 2) + ',' + chunkLength + '}[.!?,]{1}|^[\\s\\S]{1,' + chunkLength + '}$|^[\\s\\S]{1,' + chunkLength + '} '); $(document).ready(function () { var element = this; var arr = []; var txt = replaceBlank($(element).text()); while (txt.length > 0) { arr.push(txt.match(pattRegex)[0]); txt = txt.substring(arr[arr.length - 1].length); } $.each(arr, function () { var u = new SpeechSynthesisUtterance(this.trim()); window.speechSynthesis.speak(u); }); }); </script> <p class="t">MLA format follows the author-page method of in-text citation. This means that the author's last name and the page number(s) from which the quotation or paraphrase is taken must appear in the text, and a complete reference should appear on your Works Cited page. The author's name may appear either in the sentence itself or in parentheses following the quotation or paraphrase, but the page number(s) should always appear in the parentheses, not in the text of your sentence.</p> <p class="t">Joe waited for the train.</p> <p class="t">The train was late.</p> <p class="t">Mary and Samantha took the bus.</p>
Here is what i ended up with, it simply splits my sentences on the period "." var voices = window.speechSynthesis.getVoices(); var sayit = function () { var msg = new SpeechSynthesisUtterance(); msg.voice = voices[10]; // Note: some voices don't support altering params msg.voiceURI = 'native'; msg.volume = 1; // 0 to 1 msg.rate = 1; // 0.1 to 10 msg.pitch = 2; //0 to 2 msg.lang = 'en-GB'; msg.onstart = function (event) { console.log("started"); }; msg.onend = function(event) { console.log('Finished in ' + event.elapsedTime + ' seconds.'); }; msg.onerror = function(event) { console.log('Errored ' + event); } msg.onpause = function (event) { console.log('paused ' + event); } msg.onboundary = function (event) { console.log('onboundary ' + event); } return msg; } var speekResponse = function (text) { speechSynthesis.cancel(); // if it errors, this clears out the error. var sentences = text.split("."); for (var i=0;i< sentences.length;i++) { var toSay = sayit(); toSay.text = sentences[i]; speechSynthesis.speak(toSay); } }
2017 and this bug is still around. I happen to understand this problem quite well, being the developer of the award-winning Chrome extension Read Aloud. OK, just kidding about the award winning part. Your speech will get stuck if it's longer than 15 seconds. I discover that Chrome uses a 15 second idle timer to decide when to deactivate an extension's event/background page. I believe this is the culprit. The workaround I've used is a fairly complicated chunking algorithm that respects punctuation. For Latin languages, I set max chunk size at 36 words. The code is open-source, if you're inclined: https://github.com/ken107/read-aloud/blob/315f1e1d5be6b28ba47fe0c309961025521de516/js/speech.js#L212 The 36-word limit works well most of the time, staying within 15 seconds. But there'll be cases where it still gets stuck. To recover from that, I use a 16 second timer.
I ended up chunking up the text and having some intelligence around handling of various punctucations like periods, commas, etc. For example, you don't want to break the text up on a comma if it's part of a number (i.e., $10,000). I have tested it and it seems to work on arbitrarily large sets of input and it also appears to work not just on the desktop but on android phones and iphones. Set up a github page for the synthesizer at: https://github.com/unk1911/speech You can see it live at: http://edeliverables.com/tts/
new Vue({ el: "#app", data: { text: `Collaboratively administrate empowered markets via plug-and-play networks. Dynamically procrastinate B2C users after installed base benefits. Dramatically visualize customer directed convergence without revolutionary ROI. Efficiently unleash cross-media information without cross-media value. Quickly maximize timely deliverables for real-time schemas. Dramatically maintain clicks-and-mortar solutions without functional solutions.` }, methods:{ stop_reading() { const synth = window.speechSynthesis; synth.cancel(); }, talk() { const synth = window.speechSynthesis; const textInput = this.text; const utterThis = new SpeechSynthesisUtterance(textInput); utterThis.pitch = 0; utterThis.rate = 1; synth.speak(utterThis); const resumeInfinity = () => { window.speechSynthesis.resume(); const timeoutResumeInfinity = setTimeout(resumeInfinity, 1000); } utterThis.onstart = () => { resumeInfinity(); }; } } }) <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> <div id="app"> <button #click="talk">Speak</button> <button #click="stop_reading">Stop</button> </div>
As Michael proposed, Peter's solutions is really great except when your text is on different lines. Michael created demo to better illustrate the problem with it. - https://jsfiddle.net/1gzkja90/ and proposed another solution. To add one maybe simpler way to solve this is to remove line breaks from textarea in Peter's solution and it works just great. //javascript var noLineBreaks = document.getElementById('mytextarea').replace(/\n/g,''); //jquery var noLineBreaks = $('#mytextarea').val().replace(/\n/g,''); So in Peter's solution it might look the following way : utterance.text = $('#mytextarea').val().replace(/\n/g,''); But still there's problem with canceling the speech. It just goes to another sequence and won't stop.
Other suggestion do weird thing with dot or say DOT and do not respect speech intonnation on sentence end. var CHARACTER_LIMIT = 200; var lang = "en"; var text = "MLA format follows the author-page method of in-text citation. This means that the author's last name and the page number(s) from which the quotation or paraphrase is taken must appear in the text, and a complete reference should appear on your Works Cited page. The author's name may appear either in the sentence itself or in parentheses following the quotation or paraphrase, but the page number(s) should always appear in the parentheses, not in the text of your sentence. Joe waited for the train. The train was late. Mary and Samantha took the bus."; speak(text, lang) function speak(text, lang) { //Support for multipart text (there is a limit on characters) var multipartText = []; if (text.length > CHARACTER_LIMIT) { var tmptxt = text; while (tmptxt.length > CHARACTER_LIMIT) { //Split by common phrase delimiters var p = tmptxt.search(/[:!?.;]+/); var part = ''; //Coludn't split by priority characters, try commas if (p == -1 || p >= CHARACTER_LIMIT) { p = tmptxt.search(/[,]+/); } //Couldn't split by normal characters, then we use spaces if (p == -1 || p >= CHARACTER_LIMIT) { var words = tmptxt.split(' '); for (var i = 0; i < words.length; i++) { if (part.length + words[i].length + 1 > CHARACTER_LIMIT) break; part += (i != 0 ? ' ' : '') + words[i]; } } else { part = tmptxt.substr(0, p + 1); } tmptxt = tmptxt.substr(part.length, tmptxt.length - part.length); multipartText.push(part); //console.log(part.length + " - " + part); } //Add the remaining text if (tmptxt.length > 0) { multipartText.push(tmptxt); } } else { //Small text multipartText.push(text); } //Play multipart text for (var i = 0; i < multipartText.length; i++) { //Use SpeechSynthesis //console.log(multipartText[i]); //Create msg object var msg = new SpeechSynthesisUtterance(); //msg.voice = profile.systemvoice; //msg.voiceURI = profile.systemvoice.voiceURI; msg.volume = 1; // 0 to 1 msg.rate = 1; // 0.1 to 10 // msg.rate = usersetting || 1; // 0.1 to 10 msg.pitch = 1; //0 to 2*/ msg.text = multipartText[i]; msg.speak = multipartText; msg.lang = lang; msg.onend = self.OnFinishedPlaying; msg.onerror = function (e) { console.log('Error'); console.log(e); }; /*GC*/ msg.onstart = function (e) { var curenttxt = e.currentTarget.text; console.log(curenttxt); //highlight(e.currentTarget.text); //$('#showtxt').text(curenttxt); //console.log(e); }; //console.log(msg); speechSynthesis.speak(msg); } } https://jsfiddle.net/onigetoc/9r27Ltqz/
I want to say that through Chrome Extensions and Applications, I solved this quite irritating issue through using chrome.tts, since chrome.tts allows you to speak through the browser, instead of the window which stops the talk when you close the window. Using the below code, you can fix the above issue with large speakings: chrome.tts.speak("Abnormally large string, over 250 characters, etc..."); setInterval(() => { chrome.tts.resume(); }, 100); I'm sure that will work, but I did this just to be safe: var largeData = ""; var smallChunks = largeData.match(/.{1,250}/g); for (var chunk of smallChunks) { chrome.tts.speak(chunk, {'enqueue': true}); } Hope this helps someone! It helped make my application work more functionally, and epicly.
Yes, the google synthesis api will stop at some point during speaking a long text. We can see onend event, onpause and onerror event of SpeechSynthesisUtterance won't be fired normally when the sudden stop happens, so does the speechSynthesis onerror event. After several trials, found speechSynthesis.paused is working, and speechSynthesis.resume() can help resume the speaking. Hence we just need to have a timer to check the pause status during the speaking, and calling speechSynthesis.resume() to continue. The interval should be small enough to prevent glitch when continuing the speak. let timer = null; let reading = false; let readText = function(text) { if (!reading) { speechSynthesis.cancel(); if (timer) { clearInterval(timer); } let msg = new SpeechSynthesisUtterance(); let voices = window.speechSynthesis.getVoices(); msg.voice = voices[82]; msg.voiceURI = 'native'; msg.volume = 1; // 0 to 1 msg.rate = 1.0; // 0.1 to 10 msg.pitch = 1; //0 to 2 msg.text = text; msg.lang = 'zh-TW'; msg.onerror = function(e) { speechSynthesis.cancel(); reading = false; clearInterval(timer); }; msg.onpause = function(e) { console.log('onpause in ' + e.elapsedTime + ' seconds.'); } msg.onend = function(e) { console.log('onend in ' + e.elapsedTime + ' seconds.'); reading = false; clearInterval(timer); }; speechSynthesis.onerror = function(e) { console.log('speechSynthesis onerror in ' + e.elapsedTime + ' seconds.'); speechSynthesis.cancel(); reading = false; clearInterval(timer); }; speechSynthesis.speak(msg); timer = setInterval(function(){ if (speechSynthesis.paused) { console.log("#continue") speechSynthesis.resume(); } }, 100); reading = true; } }
Play sound ONLY ONCE
I'm making a small asteroid avoiding game and when my lives are over I'm executing the following code: gameover.play(); while gameover is defined like so: var gameover = new Audio('gameover.wav'); When I execute the code, it loops the sound and I want to play it just once, how would I do that? Thanks.
I believe setting the loop property to false will accomplish your goal. var birdSound = new Audio('http://www.noiseaddicts.com/samples_1w72b820/4929.mp3'); birdSound.loop = false; birdSound.play(); var birdSound = new Audio('http://www.noiseaddicts.com/samples_1w72b820/4929.mp3'); birdSound.loop = false; birdSound.play(); var statusElem = document.getElementById('status'); var startTime = Date.now(); updateStatus(); function updateStatus(){ if (birdSound.ended){ statusElem.innerText = 'Stopped'; } else { statusElem.innerText = 'Playing (' + ( ( Date.now() - startTime ) / 1000 ) + ')'; window.setTimeout(updateStatus, 50); } } Audio Status: <span id="status">Playing</span> Tested in: Chrome 44.0.2403.155 (64-bit) Firefox 40.0.2 Safari 8.0.6 Vivaldi 1.0.94.2 (for giggles)
Revisited = image.onload NOT called
Very old, but very UNsolved subject: image.onload not called. Code tells the story better than words ... Calling .html = <script> var newConnection = new MeasureConnectionSpeed(); if (newConnection.isHighSpeed()) doSomething1; else doSomething2; </script> Called .html = <script> function MeasureConnectionSpeed() { var connection = this; var imgDownloadSrc = "http://someLargeImage.jpg"; var imgDownloadSize = 943 * 1024; // bytes var startTime = 0, endTime = 0; // set later connection.isHighSpeedConnection = false; // = a Object Property // an Object Method ... // just the function declaration which is called via // connection.computeResults() connection.isHighSpeed = isHighSpeed; connection.computeResults = computeResults; // another Object Method var testImgDownload = new Image(); testImgDownload.onload = function () { endTime = (new Date()).getTime(); connection.computeResults(); } // testImgDownload.onload testImgDownload.onerror = function (err, msg) { alert("Invalid image, or error downloading"); } // We immediately continue while testImgDownload is still loading ... // the timer is started here and ended inside testImgDownload.onload startTime = (new Date()).getTime(); // This forces an attempt to download the testImgDownload and get the // measurements withOUT actually downloading to your Cache: var cacheBuster = "?nnn=" + startTime; testImgDownload.src = imgDownloadSrc + cacheBuster; function computeResults() { var speedMbps = someNumber; connection.isHighSpeedConnection = speedMbps > 20; } // computeResults // this.isHighSpeed() = isHighSpeed() function isHighSpeed() { return connection.isHighSpeedConnection; } } // MeasureConnectionSpeed </script> * EDIT #1 * Two more bits ... I decided to download Google's Chrome and test my .html locally on it. Chrome accessed the .onerror Event Handler of my original code. Safari and Firefox never did??? Another curious observation ... using Chrome, alert(err) inside my .onerror Event Handler produced "undefined". But, I did use alert(this.width) and alert(this.naturalWidth), each showing 0 ... which means it is an invalid image??? And the invalid image error even occurs if I place the src before the .onload Handler. That really is it for now! * EDIT #2 - on August 8th, 2015 * 1) I am truly very sorry I have not returned earlier ... but I began to not feel well, so got a little more physical rest 2) Anyway, I implemented Dave Snyder's wonderful IIFE code and it definitely worked ... the code within the .onload Handler properly worked and I am truly extremely grateful to Dave and all the time he provided to little-ole-me. Of course, I dumped the newConnection = new MeasureConnectionSpeed() and used Dave's IIFE approach. Now, all I have to figure out why this code is giving me about 5 Mbps speed numbers where I have 30 Mbps via my Ethernet Router. I would truly expect to see a number close. I really, really hate to have to include another API since my whole purpose of speed measurement is to decide weather to redirect to a relatively "busy" site or to a "keep it simple" version. Tons of thanks, Dave. You're my hero. John Love
This works for me in Chrome. (function(){ var imgDownloadSrc = "https://upload.wikimedia.org/wikipedia/commons/d/d8/Schwalbenschwanz_%28Papilio_machaon%29.jpg", testImgDownload = new Image(), startTime, endTime, stackOverflowLog = document.getElementById('log'); var log = function(message, str) { stackOverflowLog.innerHTML += message.replace("%s", str) + "<br>"; console.log(message, str); } testImgDownload.onload = function () { log('image loaded!'); endTime = +new Date(); log('end time: %s', startTime); log('total time: %s', (endTime - startTime)); } testImgDownload.onerror = function (err, msg) { throw "Invalid image, or error downloading"; } startTime = +new Date(); log('start time: %s', startTime); testImgDownload.src = imgDownloadSrc + "?" + startTime; log('downloading: %s', testImgDownload.src); })(); <!doctype html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Untitled Document</title> </head> <body> <pre id="log"></pre> </body> </html> Here's your code, slightly modified so it runs. image.onload seems to work fine, but isHighSpeed() is called before the image has finished downloading. It will need to be refactored / reordered so that you call isHighSpeed() after it's been set. It's common to use a callback for this kind of thing. /* for illustration */ var stackOverflowLog = document.getElementById("log"); var log = function(message, str) { stackOverflowLog.innerHTML += message.replace("%s", str) + "<br>"; console.log(message, str); } /* calling.html */ var newConnection = new MeasureConnectionSpeed(); log('newConnection.isHighSpeed()? %s', newConnection.isHighSpeed()); /* called.html */ function MeasureConnectionSpeed() { var connection = this; var imgDownloadSrc = "https://upload.wikimedia.org/wikipedia/commons/d/d8/Schwalbenschwanz_%28Papilio_machaon%29.jpg"; var imgDownloadSize = 1709360 * 8; // bits (~1.6mb * 8) var startTime = 0, endTime = 0; // set later connection.isHighSpeedConnection = undefined; // = a Object Property // an Object Method ... // just the function declaration which is called via // connection.computeResults() connection.isHighSpeed = isHighSpeed; connection.computeResults = computeResults; // another Object Method var testImgDownload = new Image(); testImgDownload.onload = function () { endTime = (new Date()).getTime(); log('endTime: %s', endTime); connection.computeResults(); } // testImgDownload.onload testImgDownload.onerror = function (err, msg) { log("!!! ERROR Invalid image, or error downloading"); } // We immediately continue while testImgDownload is still loading ... // the timer is started here and ended inside testImgDownload.onload startTime = (new Date()).getTime(); log('startTime: %s', startTime); // This forces an attempt to download the testImgDownload and get the // measurements withOUT actually downloading to your Cache: var cacheBuster = "?nnn=" + startTime; testImgDownload.src = imgDownloadSrc + cacheBuster; log('loading: %s', testImgDownload.src); function computeResults() { var duration, speed, speedMbps; duration = (endTime - startTime) / 1000; // seconds speed = imgDownloadSize / duration; // bits per second speedMbps = speed / 1000000; // megabits log('duration: %s', duration); log('speed: %s', speed); log('speedMbps: %s', speedMbps); connection.isHighSpeedConnection = speedMbps > 20; } // computeResults // this.isHighSpeed() = isHighSpeed() function isHighSpeed() { return connection.isHighSpeedConnection; } } // MeasureConnectionSpeed <pre id="log"></pre>
Asynchronous ajax calls to test the download speed
What i am trying to achieve: Testing the speed of download of a binary file using javascript. I went through this link: How to detect internet speed in Javascript? and got the idea on how this can be done. Now this involves downloading the image only once and doing the calculation to get the result. Next to get better results i tried to send the repetitive ajax calls to my apache server till 10 seconds. Get the result, calculate the speed and save the result in an array. At last calculate the average of all the speeds which gives the result. Problem is if i am using the async: false the browser becomes unresponsive while using async:true is causing the loop to go infinitely and also i am not able to figure out how calls should be made. This is the code i have written: var startTime = 0; var endTime = 0; var file = ''; var allDownloadSpeeds=[]; var timeElapsed; var t = new Date(); var x=t.getTime(); console.log(t.getHours() + " " + t.getMinutes() + " " + t.getSeconds()); t.setSeconds(t.getSeconds() + 10); var y=t.getTime(); console.log(t.getHours() + " " + t.getMinutes() + " " + t.getSeconds()); var timeOfTest=y-x; function DownloadTest() { timeElapsed = 0; var speed; while(true){ startTime = new Date().getTime(); if(timeElapsed<timeOfTest){ $.ajax({ type: "GET", url: "file.bin?id=" + startTime, dataType: 'application/octet-stream', async:false, success: function(msg) { binfile = msg; endTime = new Date().getTime(); timeElapsed+=(end - startTime); diff = (end - startTime) / 1000; bytes = msg.length; speed = (bytes / diff) / 1024 / 1024 * 8; allDownloadSpeeds.push(speed); }, }); } else{ var averageSpeed=0; for(var i=0;i<allDownloadSpeeds.length;i++){ averageSpeed+=allDownloadSpeeds[i]; } console.log("Speed " + averageSpeed/averageSpeed.length); break; } } } $(document).ready(function(){ TestDownload; }); I don't know how to handle asynchronous ajax calls. Thanks in advance for help.