I have a simple routine that creates an audiocontext node and loads it with a buffer. The onended event seems to fire at the start. I set a timer for the duration and the onended event triggers about that amount of time before the audio stops playing. Anybody know how to get a true onended event. I tried to use the destination as well as the source, same result
function playSoundFile(buffer) {
'use strict';
audioCtx.close();
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
audioSource = audioCtx.createBufferSource();
audioSource.connect(audioCtx.destination);
audioSource.buffer = buffer;
audioSource.playbackRate.value = 1;
stillPlaying(true);
audio_player = audioCtx.destination;
audioSource.addEventListener('onended', whatsUp());
console.log('start ' + showTime());
filePlayed(buffer.duration);
audioSource.start();
}
the timestamp is only 1 ms different between the play and the onended event.
filePlayed starts a timeout event to show time at beginning and end.
Okay, guest271314 had it mostly right.
the documentation is confusing. The eventListener is "ended" not "onended" and you have to use function name with the parans
so
audioSource.addEventListener('ended', whatsup);
takes care of the problem. Thanks.
Related
I'm creating a music related app using Web Audio API, which requires precisely synchronizing sound, visuals and MIDI input processing.
The sound production follows the pattern described in this article: requestAnimationFrame regularly calls a function that schedules events on the AudioContext. This works fine most of the time, except in some occasions where the audio inexplicably lags behind the visuals.
After much poking around, I ran into the AudioContext.currentTime specification which hints:
Elapsed time in this system corresponds to elapsed time in the audio stream generated by the BaseAudioContext, which may not be synchronized with other clocks in the system.
And indeed I was able to verify that these sporadic delays in the audio come down to a problem in the AudioContext clock itself, which seems to pause for a bit sometimes just after starting up. Note that this doesn't happen every time, but frequently enough to be an issue (maybe 10-15% of the time...). Each time it happens the pattern is the same: currentTime starts increasing then gets stuck at 23ms for a bit, then starts going again regularly without any further issue after accumulating a total of ~230ms of lag behind the system/wall clock...
I've created a simple script which reproduces the problem (if you want to try it, just open the console to see the output, and press any key to start the test... as the issue is sporadic you may need to retry or sometimes reload multiple times before it happens...):
<script>
const audioContext = new AudioContext();
// reference points for both clocks (JS and audioContext)
var animStartTime = null;
var audioStartTime = null;
// loop function to be called by requestAnimationFrame
function play(timestamp) {
// set animStartTime on first invocation
if (animStartTime == null) animStartTime = timestamp;
// compute elapsed time for both clocks
var animElapsedTime = timestamp - animStartTime;
var audioElapsedTime = (audioContext.currentTime - audioStartTime) * 1000;
console.log('Animation ts: ' + animElapsedTime +
', Audio ts: ' + audioElapsedTime +
', Diff: ' + (animElapsedTime - audioElapsedTime)
);
// keep this going for 1 second
if (animElapsedTime < 1000)
requestAnimationFrame(play);
}
function start() {
audioStartTime = audioContext.currentTime;
animStartTime = null; // use the timestamp provided by requestAnimationFrame
// create a simple oscillator and schedule it to produce a single beep when it starts
const osc = audioContext.createOscillator();
osc.frequency.value = 800;
osc.connect(audioContext.destination);
osc.start(audioStartTime);
osc.stop(audioStartTime + 0.03);
// launch animation loop
requestAnimationFrame(play);
}
// press any key to start the test
window.addEventListener('keypress', function(e) {start()});
</script>
And here is the console output of a bogus run, where you can see the Audio timestamp freezing up to a delay of ~230ms behind the JS main thread timestamp before starting again:
Could someone explain to me:
What is going on? Why is this freezing sporadically?
Is there anything I can do to prevent this? I can think of some ways to mitigate the issue if I can get convinced that it happens only when starting up, but without fully understanding the root cause I fear seeing these freezes happen again at other times...
This is probably https://crbug.com/693978 Resuming the context may take "some" time.
Unfortunately, they don't really wait for the context has started before resolving the Promise returned by context.resume(), so we have to resort to ugly workarounds.
One such workaround would be to wait for currentTime to start updating after context.resume() resolves before starting your animation.
const audioContext = new AudioContext();
// reference points for both clocks (JS and audioContext)
var animStartTime = null;
var audioStartTime = null;
// loop function to be called by requestAnimationFrame
function play(timestamp) {
// set animStartTime on first invocation
if (animStartTime == null) animStartTime = timestamp;
// compute elapsed time for both clocks
var animElapsedTime = timestamp - animStartTime;
var audioElapsedTime = (audioContext.currentTime - audioStartTime) * 1000;
console.log('Animation ts: ' + animElapsedTime +
', Audio ts: ' + audioElapsedTime +
', Diff: ' + (animElapsedTime - audioElapsedTime)
);
// keep this going for 1 second
if (animElapsedTime < 1000)
requestAnimationFrame(play);
}
async function start() {
// wait for the context to resume
// needs to be there, for we still handle the user-gesture
await audioContext.resume();
// though resume() might be a lie in Chrome,
// so we also wait for currentTime to update
const startTime = audioContext.currentTime;
while (startTime === audioContext.currentTime) {
await new Promise((res) => setTimeout(res));
}
// now our AudioContext is ready.
audioStartTime = audioContext.currentTime;
animStartTime = null; // use the timestamp provided by requestAnimationFrame
// create a simple oscillator and schedule it to produce a single beep when it starts
const osc = audioContext.createOscillator();
osc.frequency.value = 800;
osc.connect(audioContext.destination);
osc.start(0);
osc.stop(0.3);
// launch animation loop
requestAnimationFrame(play);
}
// using a button for a clearer snippet
document.querySelector("button")
.addEventListener('click', start);
<button>start</button>
I am using WebAudio to play a sequence of notes. I have a playNote function which works well; I send it note frequency and start and stop times for each note. The generation of the sequence parameters occurs before the actual sound starts, which is a little confusing. The function just creates an oscillator for every note. (I tried other methods and this is the cleanest).
But I would like to stop the sequence asynchronously (e.g. when an external event occurs). I tried setting up a master Gain node that could be used to gate the output, but it seems it needs to be "inside" the function, so it can't be controlled later on. If I try and turn off my gain object inside the function then it is too late - because the start & stop times have already been passed to the function.
Here is my function:
function playNote(audioContext,frequency, startTime, endTime, last) {
gainNode = audioContext.createGain(); //to get smooth rise/fall
oscillator = audioContext.createOscillator();
oscillator.frequency.value=frequency;
oscillator.connect(gainNode);
gainNode.connect(analyserScale); //analyser is global
analyserScale.connect(audioContext.destination);
gainNode.gain.exponentialRampToValueAtTime(toneOn, startTime + trf);
gainNode.gain.exponentialRampToValueAtTime(toneOff, endTime+trf);
oscillator.start(startTime);
oscillator.stop(endTime);
}
Any help appreciated!
This does it: Web Audio API: Stop all scheduled sounds from playing. The solution is to keep track of the scheduled oscillators with an array.
The function now becomes:
var oscs = []; //list of oscillators
function playNote(audioContext,frequency, startTime, endTime, last, index) {
gainNode = audioContext.createGain(); //to get smooth rise/fall
oscillator = audioContext.createOscillator();
oscillator.frequency.value=frequency;
oscillator.connect(gainNode);
//keep track of alll the oscs so that they can be switched off if scale is stopped by user
oscs[index] = oscillator;
gainNode.connect(analyserScale); //analyser is global
analyserScale.connect(audioContext.destination);
gainNode.gain.exponentialRampToValueAtTime(toneOn, startTime + trf);
gainNode.gain.exponentialRampToValueAtTime(toneOff, endTime+trf);
oscillator.start(startTime);
oscillator.stop(endTime);
}
Then code to stop the oscillators:
for(let i=0; i<oscs.length; i++) {
if(oscs[i]){
oscs[i].stop(0);
}
}
I was wondering is there possible to pause the video in actually time ?
I tested and video always pause at x.xxx not x.00
video.load();
video.play();
video.ontimeupdate = function(){
if(this.currentTime >= 3) {
video.pause();
}
};
Demo : https://jsfiddle.net/l2aelba/4gh7a058/
Any trick ?
PS: Should be good performance as possible also
I believe you cannot guarantee that the video will stop at an exact moment in time.
According to the documentation for media controller:
Every 15 to 250ms, or whenever the MediaController’s media controller
position changes, whichever happens least often, the user agent must
queue a task to fire a simple event named timeupdate at the
MediaController.
timeupdate event will fire when it can using the least often scenario. It does not give you the option to choose the exact fire times for the updates.
A trick you could do is the following: Remove the timeupdate event, set your own interval and using that check the time.
setInterval(function () {
var ct = video.currentTime;
current_time_el.innerHTML = 'Current time : ' + ct;
if(ct >= 3) {
video.pause();
}
}, 40);
This approach will force you to be more careful with your code though. (e.g clean up your interval with clearInterval() when it is not needed any more)
I'm trying to measure the time between when the user clicks the play button, triggering audio.play(), and the time when the audio actually starts to play on the client. I'm just not sure which event to listen to, as per http://www.w3schools.com/tags/ref_av_dom.asp
It seems that 'play' and 'canplay' are giving me similar times, so I'm not sure of the correct one to listen to. It's rather hard for me to test since I have a fairly quick connection. I'm using the following code:
var time;
var audio = document.createElement('audio');
var uri = 'http://www.flashkit.com/imagesvr_ce/flashkit/soundfx/Creatures/Male_zombi_Zapsplat_8347/horror_monster_zombie_male_eating_body_001.mp3'
audio.setAttribute('preload', 'metadata');
audio.src = uri;
audio.addEventListener('canplay', (evt) => {
time = Date.now() - time;
alert(time);
});
audio.play();
time = Date.now()
setTimeout(function() { audio.pause(); }, 1000);
https://jsfiddle.net/67vmr476/1/
I am working with HTML5 Audio Player. I am implementing playlist. I am using 'ended' event action to play the next audio media file. But, At the end of media file, 'ended' event is fired more than one time. (As the message I log appears more then one time.)
This is my Event Listener:
//Event Listener
function addPlayerListener() {
console.log("Recording Index: "+combinedSessionIndex);
var audio = document.getElementById("playarea");
audio.addEventListener('ended', function () {
sessionsToPlay = $("#audio_files").val().split(',');
console.log ("In Recording End Event Listener...!!!"+combinedSessionIndex);
//It Shows Length '1' not '0'
if (sessionsToPlay.length == 1) {
console.log("Returned Due to Session To Play length..!!!")
return;
}
//As CombinedSessionIndex Starts with 1
if (combinedSessionIndex == sessionsToPlay.length) {
console.log("Returned Due to All Session To Play Got Finish..!!!")
$("#audio_files").val("");
sessionsToPlay = [];
combinedSessionIndex = 0;
return;
}
var filePath = '${properties["RECORDED_SESSION_RETREIVAL_PATH"]}';
filePath = filePath + sessionsToPlay[combinedSessionIndex] + ".mp4";
combinedSessionIndex++;
audio.src = filePath;
audio.play();
}, false);
}
So, When a particular media file ends, it skips next media files and start playing next (after skipping 1,2) file. For each skipped media file, control comes in event listener, which shows that event listener is being call repeatedly while single call ends.
For the first Time I play audio file by this code:
//PlayBack Session
function play(data) {
//Just to deal with a scenario that when we play a combined session and in the mean while we start listening any other single
//session, then after completion of this single session, remaining previous combined session should not resume.
//console.log("Re-setting Player ...!!!");
//$("#audio_files").val("");
//sessionsToPlay = [];
//combinedSessionIndex = 0;
var filePath = '${properties["RECORDED_SESSION_RETREIVAL_PATH"]}';
filePath = filePath + data + ".mp4";
console.log("filePath: " + filePath);
if (data == null || data == "") {
alert("Nothing to Play ...!!!");
return;
}
$("#playarea").attr({
"src": filePath,
"autoplay": "autoplay"
})
}
And after that when this ends, further clips are played using 'ended' event listener.
I am unable to find reason for this. Please guide me if I am doing anything wrong ?
Thanks in Advance :)
Ahh My bad. I was registering the same Event Listener 'Ended' frequently (Each time user presses Play Button, while it should be just single time). That's why I was getting 'ended' events more than one time. It actually generates the 'Ended' event action for the number of times I register for 'Ended' event.
Do you not think it should consider just once ? Because our purpose is just getting notified when media file gets end. There's no logic of notifying multiple times though we register for multiple times.