I have created a site with image thumbnails of people I have photographed. When a visitor clicks on one of the thumbnails the full image is revealed using jQuery, and an audio introduction plays. I have a different audio introduction for each thumbnail/image combination - 15 at present with more being added daily.
I would like to ensure that if a visitor clicks on another thumbnail before the previous audio file has completed, that the previous audio file is stopped/paused to allow the new audio file to be played - thereby ensuring two or more tracks do not play simultaneously.
I am currently using the following snippet of code, wrapped in an anonymous function, to play each audio file individually when the appropriate thumbnail is clicked - so this snippet is duplicated for each audio file, but don't know how to ensure they do not play over one another.
$(".bridget-strevens").click(function(){
var audio = $('#bridget-strevens-intro')[0];
if (audio.paused){
audio.play();
} else {
audio.pause();
}
});
Any help you could give me would be very grateful, as I am just starting to learn jQuery, and don't have the knowledge to come up with a workable solution.
Thanks in advance for your help!
Add a .audio class to all your audio elements and loop through all of them when an audio is clicked.
$(".bridget-strevens").click(function () {
$('.audio').each(function (index, value) {
if (!value.paused) {
value.pause();
}
});
var audio = $('#bridget-strevens-intro')[0];
if (audio.paused) {
audio.play();
} else {
audio.pause();
}
});
If that seems too heavy for you then simply add the audio element in a global variable such as:
var currentAudio;
Then when a new audio is clicked, simply pause that one, play the new one and update the currentAudio variable with the new element currently being played.
var currentAudio = null;
$(".bridget-strevens").click(function () {
if(currentAudio != null && !currentAudio.paused){
currentAudio.pause();
}
var audio = $('#bridget-strevens-intro')[0];
if (audio.paused) {
audio.play();
currentAudio = audio;
} else {
audio.pause();
}
});
Update:
Thanks for the prompt responses! Grimbode, I've tried what you
suggest, and that seems to work. However is there the ability to stop
and reset rather than just pause - so if they clicked on 1 then [2]
before 1 finished, then clicked 1 again, that 1 would start from
the beginning again rather than the point at which it was paused? And
is there any way of check the state 'globally', and then add code for
each individual audio file - just to keep the amount of code and
duplication down? Thanks again!! –
Yes. Play audio and restart it onclick explains in detail how to do this. The final result would look something like this:
var currentAudio = null;
$(".bridget-strevens").click(function () {
if(currentAudio != null && !currentAudio.paused && currentAudio != this){
currentAudio.pause();
//Here we reset the audio and put it back to 0.
currentAudio.currentTime = 0;
}
var audio = $('#bridget-strevens-intro')[0];
if (audio.paused) {
audio.play();
currentAudio = audio;
} else {
audio.pause();
}
});
You can't really optimize the code much more. You're going to have apply the click event on every audio element. You're going to have to keep the current playing audio element memorized so you don't have to loop through all the audio files.
If you really want to take this further you could create a library to handle everything. Here is an example:
(function(){
var _ = function(o){
if(!(this instanceof _)){
return new _(o);
}
if(typeof o === 'undefined'){
o = {};
}
//here you set attributes
this.targets = o.targets || {};
this.current = o.current || null;
};
//create fn shortcut
_.fn = _.prototype = {
init: function(){}
}
//Here you create your methods
_.fn.load = function(){
//here you load all the files in your this.targets.. meaning you load the source
//OR you add the click events on them.
//returning this for chainability
return this
};
//exporting
window._ = _;
})();
//here is how you use it
_({
targets: $('.audio')
}).load();
Related
I am using a bit of code served by javascriptkit.com which dynamically create an audio element and load a given source file. I modified it a bit to set a loop attribute then play continuously on jQuery mouseenter(). This all works properly.
I added a function stopSound and jQuery mouseleave() to trigger that stop method, but the audio continues to play without the mouse on the element. The console does not show any errors, yet it fails.
Here is the code
jQuery(function($) {
// Mouseover/ Click sound effect- by JavaScript Kit (www.javascriptkit.com)
// Visit JavaScript Kit at http://www.javascriptkit.com/ for full source code
// http://www.javascriptkit.com/script/script2/soundlink.shtml
var mimetype = {
"mp3": "audio/mpeg",
"mp4": "audio/mp4",
"ogg": "audio/ogg",
"wav": "audio/wav"
}
function soundFx(sound='')
{
var audioElement = document.createElement('audio');
if (audioElement.canPlayType)
{
for (var i=0; i<arguments.length; i++)
{
var src = document.createElement('source');
src.setAttribute('src', arguments[i]);
if (arguments[i].match(/\.(\w+)$/i))
src.setAttribute('type', mimetype[RegExp.$1]);
audioElement.appendChild(src);
}
audioElement.playsoundFx = function(){
audioElement.setAttribute('loop','loop');
audioElement.play();
}
// this added method does not execute
audioElement.stopSound = function() {
audioElement.pause();
audioElement.currentTime = 0;
}
return audioElement;
}
else{
return {playsoundFx:function(){
throw new Error("Your browser does not support HTML5 audio");
}
}
}
}
// sound fx trigger
$.fn.runsoundFx = function(soundfile='') {
this.mouseenter(function() {
soundFx(soundfile).playsoundFx();
});
this.mouseleave(function() {
soundFx().stopSound();
});
};
});
What do I need to do in the code to get the audio to stop on mouseleave?
Update 9/26
The code now works with the checked solution provided by #PatrickEvans , be sure to read his response as I've left the original question intact.
If anyone wants to use it in a document, the methods to attach sound to an element by ID, class or element name are:
$("#myelement").runsoundFx("/url/to/file.mp3");
or attach to all images
$("img").runsoundFx("/url/to/file.mp3");
or attach same audio to multiple classes and/or IDs and element names
$(".myclass, .otherclass, #myId, strong").runsoundFx("/url/to/file.mp3");
soundFx() is going to create and return a new audio instance when it is called. It is not going to return the instance created in a previous call. You will need to modify your code to keep track of your sound instances, test to see if it was already created returning it if so, otherwise create it and add it to the tracker.
For instance you could make a Map list using the source url as a key.
var audioMap = new Map();
function soundFx(sound='') {
if(audioMap.has(sound)){
return audioMap.get(sound);
}
//rest of your creation code
//add instance to map before returning it
audioMap.set(sound,audioElement);
return audioElement;
}
And in your mouseleave pass in your source value
$.fn.runsoundFx = function(soundfile='') {
this.mouseenter(function() {
soundFx(soundfile).playsoundFx();
});
this.mouseleave(function() {
soundFx(soundfile).stopSound();
});
};
Edit 2: Here is the working code. Many thanks to Piotr for his help I couldn't have done it so effortlessly without you guys.
sceneEl.querySelector('a-sound').setAttribute('sound', {src:url3});
let playing = false;
var el = document.querySelector('a-box');
let audioEl = document.querySelector("a-sound");
var audio = audioEl.components.sound;
el.addEventListener('click', () => {
if (!playing) {
audio.playSound();
} else {
audio.stopSound();
}
playing = !playing;
})
} );
request.send( null );
}
});
Edit: I've got the sound playing from a dynamic URL (in my JSON file), but I cant seem to get the event listener function right (for playing / pausing on click).
sceneEl.querySelector('a-sound').setAttribute('sound', {src:url3});
let audioEl = document.querySelector("a-sound");
let audio = audioEl.components.sound;
sceneEl.querySelector('a-box').addEventListener('click', function () {
if(!playing) {
audio.play();
} else {
audio.pause();
audio.currentTime = 0;
}
playing = !playing;
});
} );
request.send( null );
}
});
Original: I'm using this component in A-Frame, but I'm looking to play the sound from the src in the ('a-sound') entity rather than from the asset link. The reason is because I'm loading the sound files dynamically from a JSON array so they don't exist in any list of assets. I've got all my files loading but am having trouble getting this component to hook into my loaded sceneEl.querySelector('a-sound').setAttribute('sound', {src:url3}); code. I'm thinking its just a small syntax issue but I'm not 100% sure. Could someone please look over this for a minute and tell me if its doable? This is the code (same as the link except for the (a-sound) within the querySelector.
AFRAME.registerComponent('audiohandler', {
init:function() {
let playing = false;
let audio = document.querySelector('a-sound');
this.el.addEventListener('click', () => {
if(!playing) {
audio.play();
} else {
audio.pause();
audio.currentTime = 0;
}
playing = !playing;
});
}
})
</script>
Using the <a-sound> You must handle things a bit differently.
playing / stopping the sound should be done within the sound component. You need to access it via yourEntityName.components.sound and use the playSound() and stopSound() methods.
check it out on my glitch. I set the source via the setAttribute(), and make a play / stop button.
My <a-sound> has a geometry to be a button, but You can make a <a-sound> entity, and use it like this:
let audioEl = document.querySelector("a-sound");
audioEl.setAttribute("src", "sourceURL");
let audio = audioEl.components.sound;
// play = audio.playSound();
// stop = audio.stopSound():
Furthermore, there are many issues with the nodes not being fully loaded. Check out this example:
<a-entity component></a-entity>
<a-sound></a-sound>
If the component tries to grab a reference to the document.querySelector("a-sound").components.sound, it may be undefined. If so, You should try to wait until it emits the loaded signal.
I have multiple audio files that play when clicked using the following javascript code:
function playSound(el,soundfile) {
if (el.mp3) {
if(el.mp3.paused) el.mp3.play();
else el.mp3.pause();
} else {
el.mp3 = new Audio(soundfile);
el.mp3.play();
}
}
The problem is that when two are clicked both audio files will play simultaneously. I need for the first audio file to stop playing when the second is clicked.
What do I need to add to this code.
I haven't used the sound API from HTML5 yet, so this is just to get my point across.
So, Like I said in my comment, you could add all elements to a global array and pause them like that.
var audio = [];
function playSound(el,soundfile) {
// Pause all
if (el.mp3) {
if(el.mp3.paused) {
// Pause all sounds
pauseAllSounds();
el.mp3.play();
// Add the sound to the audio array
audio.push(el);
// You should probably clean up the audio array too,
// so no two identical audio files exists in the array,
// but that's up to you! :)
}
else el.mp3.pause();
} else {
el.mp3 = new Audio(soundfile);
el.mp3.play();
}
}
function pauseAllSounds(){
audio.forEach(function(el){
if(!el.mp3.paused) el.mp3.pause();
});
}
I have created a fiddle of my function here( http://jsfiddle.net/rhy5K/10/ ) . Now i want to provide the Sound "Mute/unpute" option for users. Example if i click on "a link then sound playing like Get ready,5,4,3,2,1 should be mute and vice-versa.
I am thinking like to call a to toggle_sound function on click, but i am confuse like what should i write inside the function to mute the sound.
Please provide me a logic, or a solution for my problem.
Explanation using code example:
I want to MUTE/UnMUTE this below sound playing
var playGetReady = function (done) {
var ids = ['audiosource', 'a_5', 'a_4', 'a_3', 'a_2', 'a_1'],
playNext = function () {
var id = ids.shift();
document.getElementById(id).play();
if (ids.length) {
setTimeout(playNext, 1000);
} else {
done();
}
};
playNext();
};
Warning: This JS fiddle demo may play sound on load
Try
HTML5 Video_tag for display video with audio
or
this below example along with html5 with jquery
window.my_mute = false;
$('#my_mute_button').bind('click', function(){
$('audio,video').each(function(){
if (!my_mute ) {
if( !$(this).paused ) {
$(this).data('muted',true); //Store elements muted by the button.
$(this).pause(); // or .muted=true to keep playing muted
}
} else {
if( $(this).data('muted') ) {
$(this).data('muted',false);
$(this).play(); // or .muted=false
}
}
});
my_mute = !my_mute;
});
I think you can use:
document.getElementById(id).volume = 1;
or
document.getElementById(id).volume = 0;
I have this audio tag playing in the background, and I'm able to store the progress in seconds to a cookie.
But in no way I'm able to start the audio from that cookie. (for continuing on other pages)
$("p#sound audio").currentTime = $.cookie("audioTime");
<audio autoplay="autoplay" loop="loop" ontimeupdate="document.getElementById('tracktime').innerHTML = Math.floor(this.currentTime); $.cookie('audioTime', Math.floor(this.currentTime));">
<source src="audio/song.ogg" type="audio/ogg" />
<source src="audio/song.mp3" type="audio/mp3" />
Your browser does not support the audio tag.
</audio>
<span id="tracktime">0</span>
Does this have to do with the song being loaded again from start?
Thanks!
EDIT:
$("p#sound audio").get[0].currentTime
With .get[0], it doesn't work either.
Can someone please clear things up for me? Greatly appreciated!
You need to wait until audio source loads before you set the current time.
$(function(){
$('audio').bind('canplay', function(){
$(this)[0].currentTime = $.cookie('audioTime');
});
});
You can set the start time by adding t=<time> to the URL, as documented here: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Using_HTML5_audio_and_video#Specifying_playback_range
E.g. <audio src="http://example.com/audio.mp3#t=50></audio>
At first there is an error in your code because currentTime is not a part of jQuery (but you already know this)
$("p#sound audio").currentTime // is incorrect (refers to a property of jQuery)
$("p#sound audio")[0].currentTime // is correct (refers to a property of DOM element)
I discover that the audio tag has some strange things and can be operate differently from browser to browser, for example in Chrome.
At first you have to wait for the 'durationchange' event to be sure the length is known by the object.
After this you have to start the stream with 'play()' (if not already started) and pause it (sometimes after a short delay) with the 'pause()' function. Then you can change the 'currentTime' property with the value. After this you have to start the stream again by using the 'play()' function.
Also it is sometimes needed to load the stream by yourself by using the 'load()' function.
Something like this:
$(document).ready( function()
{
var a = $('audio:first'),
o = a[0];
a.on( 'durationchange', function(e)
{
var o = e.target;
if( o )
{
o.pause();
o.currentTime = parseInt( $.cookie("audioTime"));
o.play();
}
});
if( o )
{
o.load();
o.play();
}
});
You have to play with it to be sure what is the best in your situation, for example the resume (play again) method to delay it for a second or so.
When using this method you don't have to use the autoplay feature because most of the time it doesn't work.
Hope it helps, greetz,
Erwinus
what I found in my case is that there is an issue with context somewhere. I initialize audio under the window context but when I try to change currentTime from XMLHttpRequest response it does NOT work. I don't know the answer yet but I'm providing a clue maybe an expert in Javascript will know how to make it work.
/* initialize this.audio player */
Audio = function() {
var that = this;
// keep track of playback status
var AudioStatus = {
isPlaying : false
};
// define references to this.audio, pulldown menu, play-button, slider and time display
that.audio = document.querySelector("AUDIO");
/* load track by menu-index */
var loadTrack = function() {
if(that.audio == null){
log("audio null"); return;
}
that.audio.src = '../sounds/400.mp3';
that.audio.load();
};
/* callback to play or pause */
that._play = function() {
if(that.audio == null){
log("audio null"); return;
}
that.audio.play();
AudioStatus.isPlaying = true;
};
that._pause = function() {
if(that.audio == null){
log("audio null"); return;
}
that.audio.pause();
AudioStatus.isPlaying = false;
};
that.playPause = function() {
if (that.audio.paused) {
self._play();
}
else {
self._pause();
}
};
/* callback to set or update playback position */
that.updateProgress = function(value) {
if(that.audio == null){
log("audio null"); return;
}
that.audio.currentTime = value; // <<<--- it does NOT work if I call from XMLHttpRequest response but it DOES if it is called from a timer expired call back
};
that.isAudioPlaying = function(){
return AudioStatus.isPlaying;
};
};
This works for me.
if (!isNaN(audio.duration)) {
audio.currentTime = 0;
}
Hope it helps!
This solved it for me:
$("p#sound audio").on('loadedmetadata', () => {
$("p#sound audio").get(0).load();
});
So I could later set the currentTime without worrying about whether the audio was loaded or not.
I had a similar problem when trying to play a HLS stream through an HTML audio element.
No matter where and how I tried to set the currentTime property on the audio element, it wouldn't work until about 2.5s after calling audioElement.play().
//Calling this
htmlElement.currentTime = 5.5;
console.log('currentTime: '+htmlElement.currentTime);
//Would return 'currentTime: 0';
What did work tho, was if I called that in a timeout with 2500-3000ms delay.
After a day of debugging to pinpoint the element state in which it would allow me to set the currentTime property, so I wouldn't have to rely on a fixed timeout.
My solution was to listen to 3 HTMLMediaElement events (https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#events): loadedmetadata, loadeddata and canplaythrough and setting custom flags when they were first reported.
When all 3 were reported and the HTMLMediaElement.duration property was bigger than 0, I set the HTMLMediaElement.currentTime to my desired value inside the first HTMLMediaElement timeupdate event callback.
This can be summed up in the following code snippet:
let audioElementStates = {
'metadataloaded': false,
'dataloaded': false,
'canplaythrough': false
};
let shouldSetOldTime = true;
let audioElement = document.getById('audio-element');
audioElement.src = 'https://somedomain.com/hlsplaylist.m3u8';
audioElement.addEventListener('loadedmetadata',()=>{audioElementStates.metadataloaded = true;});
audioElement.addEventListener('loadeddata',()=>{audioElementStates.dataloaded = true;});
audioElement.addEventListener('canplaythrough',()=>{audioElementStates.canplaythrough = true;});
audioElement.addEventListener('timeupdate',()=>{
if(shouldSetOldTime &&
audioElement.duration>0 &&
audioElementStates.metadataloaded &&
audioElementStates.dataloaded &&
audioElementStates.canplaythrough){
audioElement.currentTime = 5.5;
shouldSetOldTime=false;
}
});
audioElement.play();
For reference I was using this with Vue.js in a Quasar framework mobile app packaged with Apache Cordova.
NOTE: The loadedmetadata check can probably be skipped, since loadeddata would presumably never fire before loadedmetadata.