In Chrome, the voiceschanged is firing on page load, so I don't need to call the function that has speechSynthesis.getVoices() initially in order for my empty array to be filled with voices as long as I have an event listener that calls it when voiceschanged is fired.
// Store voices
let voices = [];
function getVoices() {
voices = speechSynthesis.getVoices();
// Create an option for each voice in array
voices.forEach((voice) => {
const option = document.createElement('option');
option.value = voice.name;
option.innerText = `${voice.name} ${voice.lang}`;
voicesSelect.appendChild(option);
});
}
// Voices changed
speechSynthesis.addEventListener('voiceschanged', getVoices);
// Not needed (in Chrome at least) because voiceschanged event fires on page load, calling getVoices due to event listener (trying to figure out why)
// getVoices();
I'm just trying to understand this behavior - MDN's explanation of when voiceschanged fires doesn't explain it as far as I can tell:
The voiceschanged event of the Web Speech API is fired when the list
of SpeechSynthesisVoice objects that would be returned by the
SpeechSynthesis.getVoices() method has changed (when the voiceschanged
event fires.)
The event fires, because the list of voices changes when Chrome finishes making an API call to get the list of voices available only to Chrome users. Proof:
If I load my Speech Synthesis API-based web app, with Internet connection, I have 21 available voices, a few months ago, I only remember 10 or 15 or so.
If I do the same, without Internet connection, I only have two voices: Microsoft David Desktop and Microsoft Zira Desktop.
You probably notice that the two voices without Internet connection are rather boring and almost recognizable for being used in cheap audio production. But the Google Chrome ones are fluid and almost inflective. This event has to fire when the voices are loaded, of course. Take a quick glance at the W3C Errata in the Web Speech API Specification. Any time the voices are loaded, the voiceschanged event is fired....
voiceschanged: Fired when the contents of the SpeechSynthesisVoiceList, that the getVoices method will return, have changed. Examples include: server-side synthesis where the list is determined asynchronously, or when client-side voices are installed/uninstalled.
And, in fact, look at the last line of the MDN web docs you linked...
With Chrome however, you have to wait for the event to fire before populating the list, hence the bottom if statement seen below.
Speech Synthesis API-Based Source Code (from my open-source project PronounceThat)
Related
I want to access the list of voices that are part of the SpeechSynthesis API on desktop Chrome, Safari, and Firefox. If I open a new tab in each browser and, via the console, run:
speechSynthesis.getVoices()
...I expect an array containing 'SpeechSynthesisVoice' objects (i.e. the available voices) to be returned. Firefox and Safari behave as expected, but in Chrome the first call to getVoices() returns an empty array. I have to call the method again in order to receive the expected populated array.
Why does Chrome behave like this? Does it do some kind of lazy loading of certain web APIs? Please help me understand.
This happens because SpeechSynthesis API allows the usage of remote servers for speech synthesis and Chrome requests a list of voices from Google's servers. To fix this you need to wait when voices will be loaded and then request them again.
To do so you should to listen a voicechanged event, and then initialise your program's logic:
speechSynthesis.addEventListener("voiceschanged", () => {
const voices = speechSynthesis.getVoices()
})
I use audio-recorder-polyfill in my React project, to make possible audio recording for Safari. It seems to work in getting the recording to take place, however, no audio data gets available. The event "dataavailable" never gets fired, and no data seems to be "compiled" after stopping recording either.
recordFunc() {
navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
recorder = new MediaRecorder(stream);
// Set record to <audio> when recording will be finished
recorder.addEventListener('dataavailable', e => {
this.audio.current.src = URL.createObjectURL(e.data);
})
// Start recording
recorder.start();
})
}
stopFunc() {
// Stop recording
recorder.stop();
// Remove “recording” icon from browser tab
recorder.stream.getTracks().forEach(i => i.stop());
}
There have been a number of similar issues posted on audio-recorder-polyfill's issue tracker.
a
b
c
d
e
Root cause
One of those issues, #4 (not listed above), is still open. Several comments on that issue tracker hint that the root issue is that Safari cancels the AudioContext if it was not created in a handler for a user interaction (e.g. a click).
Possible solutions
You may be able to get it to work if you:
Do the initialisation inside a handler for user interaction (i.e. <button onclick="recordFunc()">)
Do not attempt to reuse the MediaStream returned from getUserMedia() for multiple recordings
Do not attempt more than 4 (or 6?) audio recordings on the same page (sources [1], [2] mention that Safari will block this)
Alternative libraries
You might also be able to try the StereoAudioRecorder class from the RecordRTC package, which has more users (3K) but appears less maintained, and might work
Upcoming support
If you'd prefer to stick to the MediaRecorder API and the tips above don't work for you, the good news is that there is experimental support for MediaRecorder in Safari 12.4 and up (iOS and macOS), and it appears to be supported in the latest Safari Technology Preview.
See also
The comments in this issue may also be useful
In my web app, I use the applicationCache feature. I register to the appCache events like
appCache.addEventListener(<event_name>, handleCacheEvent, false);
function handleCacheEvent(e) {
console.log('handleCacheEvent appcache event: '+ e.type+' appCache.status: '+appCache.status);
}
(in event_name I register to all events - 'cached', 'checking', 'downloading', etc).
However, in Chrome logs, I see these logs:
Application Cache Progress event (1 of 15)
...
Application Cache Progress event (15 of 15)
at the very beginning of the run, then some of my JS business logic, and after few seconds I see my logs
appcache event: progress appCache.status: 1...
It seems that the event listener are not triggered right when the status of the appCache changes, but rather later at some point. Is it so?
Yes, I've seen the same thing. When I add event listeners to those events and log them, my log message always appears after all of the browser's log message. I think this is because the browser starts loading the appcache before the contents of the page are done loading, since the appcache does its work in the background. This still gives you a chance to handle appcache events programmatically while letting the browser do its thing to honor the specification.
I am developing an extension for Google Chrome. My background script, everytime, authorizes on a server that uses the XMPP API, and subscribes for a PubSub node. I need to unsubscribe on the exit, otherwise the dummy subscriptions will remain on the server. Is There any onBrowserClose event in the Google Chrome Extension APIs?
There is no such event in the Chrome Extension API.
There is however a chrome.windows.onRemoved event that fires each time a window closes. I figured you could check in this event if you closed the last window, but unfortunately due to the asynchronous nature of Chrome this doesn't work.
What I tried was running a simple AJAX request in the onRemoved event handler. The AJAX request never got to the server, as Chrome had already closed before running the event (or just disregarded it).
Making the final answer be: No, currently you can't, as far as I know. You might want to star the following bug report at http://crbug.com/30885 to get noticed on updates.
If you catch the case when the number of open tabs is 0, you can treat that as a Chrome onClose event. In my case, I have to cancel a desktop notification before Chrome closes because it was crashing otherwise.
This is how I did it:
1. Initialize a variable num_tabs by using the following:
chrome.tabs.getAllInWindow( null, function( tabs ){
console.log("Initial tab count: " + tabs.length);
num_tabs = tabs.length;
});
2. Increment num_tabs when a tab is created:
chrome.tabs.onCreated.addListener(function(tab){
num_tabs++;
console.log("Tab created event caught. Open tabs #: " + num_tabs);
});
3. Decrement num_tabs when a tab is removed and run your browser onclose event handler if num_tabs = 0
chrome.tabs.onRemoved.addListener(function(tabId){
num_tabs--;
console.log("Tab removed event caught. Open tabs #: " + num_tabs);
if( num_tabs == 0 )
notification.cancel();
});
Adding a browser close event is a pretty frequent request. Star http://crbug.com/30885 for updates. And read the bug report for a clever hack to detect when the browser is shut down via a key press.
This one works for me:
chrome.windows.onRemoved.addListener(function(windowId){
alert("!! Exiting the Browser !!");
});
It takes chrome.windows rather than chrome.tabs.
TL;DR: Try window.onunload event, it works for some cases.
As it was mentioned before we generally can't handle something like onBrowserClose event and prevent browser from closing. But for some cases we can use window.onunload event for doing something synchronously, and it does work if it really synchronously.
From my experience you can at least:
Save some information in (for example logs) in HTML5 localStorage (which is synchronous).
Call some asynchronous chrome extension API functions but you can't get a result. (It works for me! Yay!)
Perform synchronous XMLHTTPRequest (yeah, sometimes it works).
Is it possible to report the time spend on a page back to server when the visitor leaves the page by closing the tab or the browser? Using Javascript.
And it will work on Firefox, Google Chrome, Safari, Opera, and IE8 (IE7 and 6 are not important).
But it will work in any case; even while the browser is closing, or it is typed another address.
You can try windows.onbeforeunload but there is no guarantee that it will work in all cases.
Anyway I suggest you use google analytics which has that feature among many others (I doubt you can do better than google!). It's free.
If you want to implement this yourself, you need to decide when to get the start time--you can put a script tag at the top of the page, which may execute before the content has rendered, on the onload event, which will wait until all images and scripts have loaded, or something like the JQuery ready event, which occurs when the DOM is ready, but doesn't wait for resources to load. At this point, you'd want to record a start time:
<script>var startTime = new Date().getTime();</script>
Then you can listen for the onunload or onbeforeunload event and use an image to send a GET request with the info:
<script>
window.onunload = function() {
var i = new Image();
var timeSpentMilliseconds = new Date().getTime() - startTime;
i.src = '/pagetime?timespent=' + timeSpentMilliseconds;
}
</script>
You'll have to do some testing to see whether the requests are always sent--I'm sure sometimes the browser closes or the tab switches before they finish firing.
You could also try PiWik which offers you similar statistics about the visitor of you website.
Why not use Google Analytics directly?
Besides what you want, it provides much more data for your analytics.
And, it is free.
Regards,
freezea.