I would like use an HTML5 audio element such that it has multiple "channels." Normally, with a simple audio tag, if I press the play button once, it will play; however, if I press play again while the audio is still playing, it won't play the again. Essentially, I want the same sound to be able to be played again simultaneously unless all of the channels are playing.
To do this, I'm loading many audio tags. Here is my code to load the audio files:
function loadAllAudios(number) {
toLoad = number;
loaded = 0;
audios = [];
for (var i = 0; i < number; ++i) {
// Random test audio file here
loadAudio("http://www.w3schools.com/html/horse."); // Leave off extension
}
}
And the loadAudio function is as follows:
function loadAudio(source) {
var aud = document.createElement("audio");
aud.addEventListener("canplaythrough", function() {
++loaded;
});
aud.src = source + (aud.canPlayType("ogg") ? "ogg" : "mp3");
audios.push(aud);
};
To keep track of which audio element is playing, I have an array called audios. The elements at the beginning of the array have probably ended, while those at the end are probably still playing. When an audio element starts playing, it is moved from the beginning to the end.
This is the code that gets executed when you press play:
console.log(audios);
// Make sure everything is loaded, and that audios isn't an empty array
if (loaded === toLoad && audios && audios.length) {
var curAudio = audios[0];
curAudio.play();
// Move the first element to the end of the array
// so that we don't play it again while it is still playing
audios.push(audios.shift());
}
The jsfiddle is here: http://jsfiddle.net/prankol57/XgRH4/1/
My problem is that, for example, if I load three channels, after clicking play 4 times, the code stops working, even if all the audios have ended. I thought it might have been that the element isn't moved to the end correctly, but a console.log shows that all the audio elements are still there.
Edit: Of the browsers I tested in (Chrome, Firefox, and Internet Explorer 11), the code works only in Internet Explorer 11.
canplaythrough event will fire more than once for single file, causing unexpected ++loaded (even when the file was already loaded) and thus loaded === toLoad condition will return false.
Related
How to add a listener on audio/video start?
I can do it like:
$audio.on("play", function() {
console.log("audio played");
});
where $audio is a jQuery object representing the single DOM audio element.
but it will also run when we resume the video. I want it to run on start only.
One way I can do it is:
$audio.on("play", function() {
if(this.currentTime === 0) {
console.log("audio started);
}
});
but this will run multiple times when we play/pause the audio on the start.
Is there any better way to do this? The listener should only work on audio start and audio replayed, not when the user manually drags the seek bar to the beginning of the source.
Store a flag into the data-* attribute of the targeted element:
const $audio = $('.audio');
$audio.on("play", function() {
if ($(this).data('once-played')) return; // Do nothing if data exists.
// Your code here
console.log("Audio started for the first time");
// Finally, add a flag.
$(this).data('once-played', true);
});
<audio class="audio" src="http://upload.wikimedia.org/wikipedia/en/4/45/ACDC_-_Back_In_Black-sample.ogg" autoplay controls loop></audio>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Actually, it might be sounding confusing because I was under the impression that there could be a possibility that currentTime could still return 0 if we do it in a fraction of seconds. But after reading #freedomn-m, #roko-c-buljan, #rory-mccrossan comments, I got it that it will never be possible that currentTime returns 0 when we play-pause-play the video immediately.
Now, let's say I want to track how many times a user has watched the video. The requirements might sound weird but by watched I only meant started/replayed. It doesn't count if the user manually drags the seek bar to the beginning of the source.
Here is how I implemented it finally using all your points:
const $audio = $('.audio');
$audio.on("play", function() {
if ($(this).data('once-played')) return; // Do nothing if data exists.
// Your code here
console.log("Audio started/replayed");
// Finally, add a flag.
$(this).data('once-played', true);
});
$audio.on("ended", function() {
$(this).data('once-played', false);
});
I'm using a <video> for a background hero video. It starts at 0:00. When it reaches the end of the 0:11 clip, it pops back to :02 and repeats from there. I've accomplished it with:
document.getElementById('bgvid').addEventListener('ended',repeatVid,false);
function repeatVid(e) {
var mediaElement = document.getElementById('bgvid');
mediaElement.currentTime = 2;
mediaElement.play();
}
I'm only concerned with the desktop experience, since it swaps to a fallback image on tablet/mobile. This works with Chrome, Firefox, and even IE. Safari, however, repeats back from 0:00 instead of :02.
Putting the whole function on a button onclick instead of an addeventListener works. Reassigning currentTime doesn't appear to be the problem. I've tried putting the whole thing inside video.oncanplaythrough = function () {. I've tried changing the .addEventListener('ended') to $('video').on('ended',function(){ or video.onended = function () {.
It seems like .play() always sends you back to 0:00, except I can get it to work on('click'), so there's something up. Thanks for your help!
The play() function in Safari seems to reset the currentTime to zero.
If you play() the video and set the currentTime directly after that, it works like expected:
document.getElementById('bgvid').addEventListener('ended',repeatVid,false);
function repeatVid(e) {
var mediaElement = document.getElementById('bgvid');
mediaElement.play();
mediaElement.currentTime = 2;
}
I'm getting different results i Firefox and Chrome when using <audio> and <video> with preload="none" and then trying to play from Javascript.
Let's say i was using preload="auto" or preload="metadata" :
audio.src = "filename";
audio.play();
That seems to work fine in both Firefox and Chrome but i want to use preload="none" and then Chrome dossent play.
So i'm trying this code with preload="none" :
audio.src = url;
audio.load();
audio.addEventListener('canplay', function(e) {
audio.play(); // For some reason this dossent work in Firefox
}, false);
audio.play(); // Added this so Firefox would play
I don't know if that's the correct way to do it.
I'm using :
Firefox 20.0.1
Chrome 25.0.1364.172 m
I made a demo : http://netkoder.dk/test/test0217.html
Edit :
In the 2nd audio player (on the demo page) it seems that when using preload="none" you have to use load().
But is it correct to just use play() right after load() or is the correct way to use an event to wait for the file to load before playing it ?
In the 3rd audio player it seems Firefox 20.0.1 dossent support the canplay event correctly when used with addEventListener() because it dossent trigger after load(), it triggers after play() and also triggers when scrubbing though the sound which dossent seem to be the way the canplay should work.
Using .oncanplay does work.
So the following code seems to work :
function afspil2(url) {
afspiller2.src = url;
afspiller2.load(); // use load() when <audio> has preload="none"
afspiller2.play();
}
function afspil3(url) {
afspiller3.src = url;
afspiller3.load(); // use load() when <audio> has preload="none"
//afspiller3.addEventListener('canplay', function() { // For some reason this dossent work correctly in Firefox 20.0.1, its triggers after load() and when scrubbing
// afspiller3.play();
//}, false);
afspiller3.oncanplay = afspiller3.play(); // Works in Firefox 20.0.1
}
I updated the demo to include the changes : http://netkoder.dk/test/test0217.html
My way of adding addEventListener inside the afspil3() function dossent seem good because the first time the function is called the code inside addEventListener is called 1 time. The second time the function is called the code inside addEventListener is called 2 time and then 3 times and so on.
It's because your audio tags are missing the required src attribute, or <source> tags. When I added them in your demo, all 3 players immediately began working in both Chrome and FF.
Also, I recently discovered that src cannot be an empty string and subsequently changed with JS. If there's a reason you can't set the src in the HTML, your best alternative, IMO, is to create the audio elements with Javascript:
var audio = new Audio();
audio.src = url;
audio.controls = true;
audio.preload = false;
// and so on
Edit: Ok. It seems that in Chrome, when the HTML is preload="none" it is necessary to call load() before playing when the src is changed. Your second audio doesn't preload, so your function needs to be this:
function afspil2(url) {
afspiller2.src = url;
afspiller2.load(); // add load call
afspiller2.play();
}
Then, it seems that in Firefox, it is necessary to set preload="auto"; when attaching an event to the canplay event, like in the 3rd function.
function afspil3(url) {
afspiller3.src = url;
afspiller3.preload = "auto";
afspiller3.load();
afspiller3.addEventListener('canplay', function(e) {
this.play(); // For some reason this dossent work in Firefox
}, false);
}
That just seems very strange, but I tested it multiple times, and each time it would play if preload="auto" is called, but would not play if it isn't called. Note that it wasn't necessary for the 2nd player, which was also preload="none" in the HTML tag.
Finally, Chrome has some odd behaviors if there are multiple <audio> elements on the page. For all three players, reloading the page and clicking "the big electron" link would play correctly.
Reloading and then clicking "Yoda" on the 2nd or 3rd player won't do anything, but it WILL play for the first player. But, if the top player is played first by any means - play button or either link - then the other two "Yoda" links will suddenly work.
Also, if you click a 2nd or 3rd "Yoda" link first after reload, and then click the top player, the previously clicked "Yoda" (that didn't previously play) will begin to play on its own after the top player stops.
Suffice it to say they have some bugs to work out.
The correct way in my opinion would mean using an existing solution, like http://mediaelementjs.com/
If your really interested in the details on the best way to play audio and video with js then look at the source:
https://github.com/johndyer/mediaelement/tree/master/src/js
https://github.com/johndyer/mediaelement/blob/master/src/js/me-mediaelements.js
Im trying to make a video player work in all browsers. There is
more then one video and every time you click on demo reel it plays the
video and if you click the video 1 the other video plays. How can i
make them both work in all browsers? Here is my html and javascript
html
<video id="myVideo" controls autoplay></video>
<div>
Demo Reel</div>
video 1</div>
</div>
javascript
function changeVid1() {
var changeStuff = document.getElementById("myVideo");
changeStuff.src = "video/demoreel.mp4"
}
function changeVid2() {
var changeStuff = document.getElementById("myVideo");
changeStuff.src = "video/video1.mp4";
}
After you switch the source of the video, you need to run .load() on it to force it to load the new file. Also, you need to provide multiple formats, because there is no video codec supported by all browsers.
First, set up your sources like this:
var sources = [
{
'mp4': 'http://video-js.zencoder.com/oceans-clip.mp4',
'webm':'http://video-js.zencoder.com/oceans-clip.webm',
'ogg':'http://video-js.zencoder.com/oceans-clip.ogv'
}
// as many as you need...
];
Then, your switch function should look like this:
function switchVideo(index) {
var s = sources[index], source, i;
video.innerHTML = '';
for (i in s) {
source = document.createElement('source');
source.src = s[i];
source.setAttribute('type', 'video/' + i);
video.appendChild(source);
}
video.load();
video.play(); //optional
}
See a working demo here.
This gives the browser a list of different formats to try. It will go through each URL until it finds one it likes. Setting the "type" attribute on each source element tells the browser in advance what type of video it is so it can skip the ones it doesn't support. Otherwise, it has to hit the server to retrieve the header and figure out what kind of file it is.
This should work in Firefox going back to 3.5 as long as you provide an ogg/theora file. And it will work in iPads, because you only have one video element on the page at a time. However, auto-play won't work until after the user clicks play manually at least once.
For extra credit, you can append a flash fallback to the video element, after the source tags, for older browsers that don't support html5 video. (i.e., IE < 9 - though you'll need to use jQuery or another shim to replace addEventListener.)
Having
var audio = new Audio("click.ogg")
I play the click sound when needed by
audio.play()
However, sometimes user is so fast that a browser does not play the audio at all (probably when still playing a previous play request). Is this issue related to preload?
How can I force a browser to stop playing and start over? There is no stop, just pause in HTML5 audio component, correct? What workaround can be used here?
Update - Additional note:
I have multiple checkbox-like div elements with a touchend event. When such event is triggered, the elements visually change, a sound is played and an internal variable is set accordingly. If user tap on these elements slowly, everything works nicely. If tap fast, the sound is often completely skipped...
The simplest solution is to just reset the audio currentTime and ensure it's playing using the play() method. Checking if the audio is playing is not necessary as subsequent play() invocations will not do anything.
audio.currentTime = 0;
audio.play();
This is the code I've been using and it's working for me:
if(audioSupport.duration > 0 && !audioSupport.paused){
//already playing
audioSupport.pause();
audioSupport.currentTime = 0;
audioSupport.play();
}else{
//not playing
audioSupport.play();
}
I noticed that on Firefox, playing a sound again and again really fast (like a short ticking sound) will skip beats often. The best solution I got was to simply call cloneNode and play each sound that way. Its not perfect (compared to Chrome where it sounds flawless):
var audio = document.getElementById('myaudio');
setInterval(function() {
audio.cloneNode().play();
}, 100);
The only way i found how to play a short sound very quickly (so quick that the 2nd sound starts before the first ends) is to actually load 5 or 10 and if you have to play again but are already playing, just go to the next, which is not playing:
var soundEls = [];//load 10 audios instead of 1
for(var i=0;i<10;i++){
var soundEl = document.createElement('audio');
soundEl.src = url;
soundEl.preload = 'auto';
$(this._soundEl).append(soundEl);
soundEls.push(soundEl);
}
var soundElIndex = 0;
return {play:function(){
var soundEl = soundEls[soundElIndex];
if(soundEl.duration > 0 && !soundEl.paused){
//already playing, switch to next soundEl
soundElIndex++;
if(!soundEls[soundElIndex]) soundElIndex=0;
soundEls[soundElIndex].play();
}else{
//not playing
soundEl.play();
}
}};
Result of this is you can actually play the same sound over itself.
Probably not the right way to do it though.