JS/JQuery text to speech: How to chain audios? - javascript

Given n different words:
var text = [ "bèijing Beijing Shanghai"]
var words= [ "bèijing", "Beijing", "Shanghai" ];
Given n different .ogg audio files with a known locations :
<!-- AUDIOS FILES -->
<audio id="id1" src="/audio/Zh-bèijing.ogg"></audio>
<audio id="id2" src="/audio/Zh-Beijing.ogg"></audio>
<audio id="id3" src="/audio/Zh-Shanghai.ogg"></audio>
I use JS getElementById('...').play(), which I run using an onclick event over an HTML element:
<!-- HTML -->
<div id="play" onClick="document.getElementById('id1').play();">
<button>Play</button>
</div>
This allow me to play one audiofile.
Using one single onlick event, how play the first audio, then when done chain to it the play of a second audio ?
Starting fiddles with assets there : http://jsfiddle.net/J9wAB/
Note: I tried document.getElementById('id1').play().done(document.getElementById('id2').play()), but it plays both files simultaneously.
EDIT: I accepted one answer working on Firefox. Yet, this addEventListener based answer fails on on some browsers. An alternative promise base answer may be more successful.

Something like this :
$('#play').data('audio', $('audio:first')).on('click', function() {
var self = this;
$(this).data('audio').on('ended', function() {
var n = $('audio').eq( $(self).data('audio').index('audio') + 1 );
$(self).data('audio', n.length ? n : $('audio:first'));
}).get(0).play();
});
FIDDLE
It gets the first audio element in the document, and stores it in jQuery's data, and once it's played (the onended event) it get's the next audio element in the DOM and the next time the button is clicked, it playes that, and when there are no more audio elements, it starts from the beginning again.
If you really wan't to use the ID instead, it would be something like :
var audio = 1
$('#play').on('click', function() {
$('#id' + audio).get(0).play();
audio++;
});
FIDDLE
To play all three on a single click, one after the other, you'd do
$('#play').on('click', function() {
$('audio').on('ended', function() {
$('audio').eq($(this).index('audio')+1).get(0).play();
}).get(0).play();
});
FIDDLE

No jQuery needed, just listen for the ended event
The simple case is the following:
document.getElementById('id1').addEventListener('ended', function(){
document.getElementById('id2').play();
});
Abstracted, it would look like http://jsfiddle.net/J9wAB/13/
function chain(ids/* id, id, ... */) {
function setHandler(first, next) {
document.getElementById(first).addEventListener('ended', function(){
document.getElementById(next).play();
});
}
for (var i = 0; i < arguments.length - 1; i++) {
setHandler(arguments[i], arguments[i+1]);
}
}
chain('id1', 'id2', 'id3');

You can use the onended="yourcallback()" to start the next audio.
Look at this answer please: https://stackoverflow.com/a/7652194/2707424

Related

jQuery problem referencing id - Using Howler.js

I have a maze I'm struggling to sort:
I have this grid with 12 anchor tags
for (let i = 1; i <= 12; i++) {
$("#minuetti").append(
`<div class="col-6 col-md-3"> <a id="minuetto${i}" class="btn btn-primary btn-lg mb-1 minuetto">Minuetto ${i}</a></div>`
);
}
Each one of those will play a different mp3 file with the code below using Howler.js.
$(".minuetto").on("click", function() {
let id = this.id;
let minuettoPath = `assets/music/${id}.mp3`;
let cell = new Howl({
src: [minuettoPath],
onplay: function() {
$(`#${id}`).text("Stop");
$(".minuetto").addClass("disabled");
$(`#${id}`).removeClass("disabled");
$(`#${id}`).removeClass("minuetto");
$(`#${id}`).on("click", function() {
cell.stop();
});
},
onend: function() {
$(".minuetto").removeClass("disabled");
$(`#${id}`).text(`Minuetto ${id.slice(8)}`);
$(`#${id}`).addClass("minuetto");
this.unload();
}
});
cell.play();
});
My problem:
I'm trying to change the text of the button clicked and keep it enabled.
I'm disabling all remaining (11) buttons until the end of the mp3 file.
But when I click on the enabled button (the one playing) instead of stopping my mp3, it restarts.
What am I doing wrong with my jQuery here? I'm referencing to my id and calling the function to do cell.stop() but for some reason jQuery runs the playing again, even though I removed the class minuetto from the playing button.
I'm really puzzled. Please help?
Use just const sound = new Howl({src: mp3.path});
Create a element using jQuery's Object Element constuctor $({})
Assign the needed events like click(), play(), and stop() that you can trigger when needed and perform your desired actions
Use Howler's sound.playing() to determine which trigger callback is needed
// Use an array of objects instead, otherwise if one day
// you decide to remove a numeric song you'll have to replace it with another.
// This also allows you to change the order of songs:
const mp3list = [
{name:'Enter Sandman', path:'https://upload.wikimedia.org/wikipedia/en/3/39/Metallica_-_Enter_Sandman.ogg'},
{name:'Back in Black', path:'http://upload.wikimedia.org/wikipedia/en/4/45/ACDC_-_Back_In_Black-sample.ogg',},
{name:'U2 - One', path:'https://upload.wikimedia.org/wikipedia/en/5/5e/U2_One.ogg',},
];
let $allMP3 = [];
const $newMP3 = mp3 => {
// HOWL
const sound = new Howl({src: mp3.path});
// $ ELEMENT
return $('<div>', {
class: 'minuetto',
text: mp3.name,
on: {
click() {
$allMP3.forEach($el => $el.not(this).trigger('stop'));
$(this).trigger(sound.playing() ? 'stop' : 'play');
},
play() {
$(this).text("Stop");
sound.play();
},
stop() {
$(this).text(mp3.name);
sound.stop();
}
}
});
};
$allMP3 = mp3list.map($newMP3); // Populate array of $ elements
$("#minuetti").append($allMP3); // Append once!
<div id="minuetti"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/howler/2.1.3/howler.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>

Calling src from nearest audio tag to JavaScript function without ID

I'm attempting to play multiple locally stored mp3 files on a single html page. I want a single JavaScript function in a separate js file triggered onclick that will play the correct file referenced in the adjacent audio tag. The audio just needs to play through once every time it is tapped/clicked.
I've tried using querySelector and various other techniques to find the nearest audio tag, but it always defaults to the audio tag at the top of the page. How can I limit the scope of querySelect, or is there another method of doing what I want? I don't want to have to create a unique ID for every audio tag because there are dozens of pages like this I am making.
function PlaySound() {
var sound = document.querySelector(".audio");
sound.play()
}
<audio class="audio" src="../../audio/na.mp3" autostart="false"></audio><a onclick="PlaySound()">나</a>
<audio class="audio" src="../../audio/gak.mp3" autostart="false"></audio><a onclick="PlaySound()">각</a>
First of all, you should use a button, not an anchor tag for these. They are buttons, since they only do an action on the page. Links are something that takes you to another page.
Now for your issue: when an event listener is invoked, such as your PlaySound when onclick happens, you can give it a this argument, which is the DOM instance of the button clicked.
Now your audio is a simple previousElementSibling away from grabbing, meaning and you can easily invoke audio.play() on it.
function play (button) {
console.log('Button:', button.innerText)
const audio = button.previousElementSibling
console.log('Audio', audio)
}
<audio src="1"></audio>
<button onclick="play(this)">1</button>
<audio src="2"></audio>
<button onclick="play(this)">2</button>
What you could do is store the information that are held by the audio tags inside a JavaScript array and dynamically create the list of audio and the respective buttons.
var audioList = [{
src: "../../audio/na.mp3",
text: "나"
}, {
src: "../../audio/gak.mp3",
text: "각"
}];
var container = document.querySelector("#container");
audioList.forEach(function(audio) {
var audioElem = document.createElement("audio");
var button = document.createElement("button");
audioElem.setAttribute("autostart", false);
audioElem.setAttribute("src", audio.src);
button.textContent = audio.text;
audioElem.className = "audio";
button.addEventListener("click", getButtonListener(audioElem));
container.appendChild(audioElem);
container.appendChild(button);
});
function getButtonListener(audioElem) {
return function() {
audioElem.play();
console.log("Playing:" + audioElem.src);
};
}
<div id="container"></div>
Or you could just do:
var audioList = {
"나": "../../audio/na.mp3",
"각": "../../audio/gak.mp3"
};
function openAudio(target){
document.querySelector("audio[src='" + audioList[target.textContent] + "']").play();
console.log("Playing: " + audioList[target.textContent]);
}
<audio class="audio" src="../../audio/na.mp3" autostart="false"></audio>
<button onclick="openAudio(this)">나</button>
<audio class="audio" src="../../audio/gak.mp3" autostart="false"></audio>
<button onclick="openAudio(this)">각</button>

how to repeat same Javascript code over multiple html elements

Note: Changed code so that images and texts are links.
Basically, I have 3 pictures all with the same class, different ID. I have a javascript code which I want to apply to all three pictures, except, the code needs to be SLIGHTLY different depending on the picture. Here is the html:
<div class=column1of4>
<img src="images/actual.jpg" id="first">
<div id="firsttext" class="spanlink"><p>lots of text</p></div>
</div>
<div class=column1of4>
<img src="images/fake.jpg" id="second">
<div id="moretext" class="spanlink"><p>more text</p></div>
</div>
<div class=column1of4>
<img src="images/real.jpg" id="eighth">
<div id="evenmoretext" class="spanlink"><p>even more text</p></div>
</div>
Here is the Javascript for the id="firsttext":
$('#firstextt').hide();
$('#first, #firsttext').hover(function(){
//in
$('#firsttext').show();
},function(){
//out
$('#firsttext').hide();
});
So when a user hovers over #first, #firsttext will appear. Then, I want it so that when a user hovers over #second, #moretext should appear, etc.
I've done programming in Python, I created a sudo code and basically it is this.
text = [#firsttext, #moretext, #evenmoretext]
picture = [#first, #second, #eighth]
for number in range.len(text) //over here, basically find out how many elements are in text
$('text[number]').hide();
$('text[number], picture[number]').hover(function(){
//in
$('text[number]').show();
},function(){
//out
$('text[number]').hide();
});
The syntax is probably way off, but that's just the sudo code. Can anyone help me make the actual Javascript code for it?
try this
$(".column1of4").hover(function(){
$(".spanlink").hide();
$(this).find(".spanlink").show();
});
Why not
$('.spanlink').hide();
$('.column1of4').hover(
function() {
// in
$(this).children('.spanlink').show();
},
function() {
// out
$(this).children('.spanlink').hide();
}
);
It doesn't even need the ids.
You can do it :
$('.column1of4').click(function(){
$(this); // the current object
$(this).children('img'); // img in the current object
});
or a loop :
$('.column1of4').each(function(){
...
});
Dont use Id as $('#id') for multiple events, use a .class or an [attribute] do this.
If you're using jQuery, this is quite easy to accomplish:
$('.column1of4 .spanlink').hide();
$('.column1of4 img').mouseenter(function(e){
e.stopPropagation();
$(this).parent().find('.spanlink').show();
});
$('.column1of4 img').mouseleave(function(e){
e.stopPropagation();
$(this).parent().find('.spanlink').hide();
});
Depending on your markup structure, you could use DOM traversing functions like .filter(), .find(), .next() to get to your selected node.
$(".column1of4").hover(function(){
$(".spanlink").hide();
$(this).find(".spanlink, img").show();
});
So, the way you would do this, given your html would look like:
$('.column1of4').on('mouseenter mouseleave', 'img, .spanlink', function(ev) {
$(ev.delegateTarget).find('.spanlink').toggle(ev.type === 'mouseenter');
}).find('.spanlink').hide();
But building on what you have:
var text = ['#firsttext', '#moretext', '#evenmoretext'];
var picture = ['#first', '#second', '#third'];
This is a traditional loop using a closure (it's better to define the function outside of the loop, but I'm going to leave it there for this):
// You could also do var length = text.length and replace the "3"
for ( var i = 0; i < 3; ++i ) {
// create a closure so that i isn't incremented when the event happens.
(function(i) {
$(text[i]).hide();
$([text[i], picture[i]].join(',')).hover(function() {
$(text[i]).show();
}, function() {
$(text[i]).hide();
});
})(i);
}
And the following is using $.each to iterate over the group.
$.each(text, function(i) {
$(text[i]).hide();
$([text[i], picture[i]].join(', ')).hover(function() {
$(text[i]).show();
}, function() {
$(text[i]).hide();
});
});
Here's a fiddle with all three versions. Just uncomment the one you want to test and give it a go.
I moved the image inside the div and used this code, a working example:
$('.column1of4').each(function(){
$('div', $(this)).each(function(){
$(this).hover(
function(){
//in
$('img', $(this)).show();
},
function(){
//out
$('img', $(this)).hide();
});
});
});
The general idea is 1) use a selector that isn't an ID so I can iterate over several elements without worrying if future elements will be added later 2) locate the div to hide/show based on location relational to $(this) (will only work if you repeat this structure in your markup) 3) move the image tag inside the div (if you don't, then the hover gets a little spazzy because the positioned is changed when the image is shown, therefore affecting whether the cursor is inside the div or not.
EDIT
Updated fiddle for additional requirements (see comments).

HTML5 Audio Tag Changing Src when finished. jQuery

I have created a audio element with a ul. In the ul is list items which are linked to different songs. I have gotten the song to change when the item is clicked and it all works however i would like the song to also change when the current playing song finishes. Below is my html and js.
HTML:
<div id="mp3_player">
<div id="audio_box">
<audio id="audioPlayer" preload="auto" src="" controls="controls"></audio>
</div>
</div>
<ul id="songSelector">
<li>song1</li>
<li>song2</li>
<li>song3</li>
<li>song4</li>
</ul>
The source doesn't have a value becasuse it is added in another part of my script.
JS:
$(audio).bind('ended', function() {
var testing = $('#songSelector').next().attr('href');
alert(testing);
})
The alert returns undefined when the song finished when it should return the link on the next li element. I just cant figure out what i am doing wrong.
Thanks in advanced.
Right now, $('#songSelector').next() is going to whatever is after #songSelector in the dom. You probably need to keep track of which one is "current" in another variable.
var currentSong = $('#songSelector').find('a:first');
$(audio).bind('ended', function() {
var next = currentSong.next();
if ( next.length ) {
currentSong = next;
} else {
/* uncomment this if you want it to loop back to the beginning */
// currentSong = $('#songSelector').find('a:first');
}
var testing = currentSong.attr('href');
alert(testing);
});
Then you just need to update currentSong in your click handler as well.

New to Javascript, need help with HTML5 audio "playlist"

im trying to create a sort of playlist feature that will work on the iPhone using a combination of HTML5 and javascript. I'm very new to the platform and I was hoping maybe someone here could help me identify the problem with my code. The first song properly autoplays, but when it ends the next one does not load and play. The javascript code for my site is provided below, thanks in advance. (my apologies if this code is terribly messed up, i assembled this from what iv learned and what i could find different places online)
<script type="text/javascript">
var songname="Bottoms Up";
var counter=1;
var totalsongs=3;
while (counter<=totalsongs-1) {
document.write(<h2>songname</h2>);
switch (counter) {
case 1:
var nextSong="audio/Hey.mp3";
var audioPlayer=document.getElementById('audioPlayer');
audioPlayer.onend = function() {
audioPlayer.src = nextSong;
songname="Hey";
counter=(counter+1);
if(counter>totalsongs-1) {
counter=1;
} //if close
} //onend function close
case 2:
var nextSong="audio/I Like It.mp3";
var audioPlayer=document.getElementById('audioPlayer');
audioPlayer.onend = function() {
audioPlayer.src = nextSong;
songname="I Like It";
counter=(counter+1);
if(counter>totalsongs-1) {
counter=1;
} //if close
} //onend function close
} //switch close
} //while close
</script>
<center><audio src="audio/Bottoms Up.mp3" autoplay controls /></center>
A few things:
Give the audio element an id:
<audio src="audio/Bottoms Up.mp3" autoplay controls id="audioPlayer" /></center>
Use arrays instead of many variables:
var songs=({{"title": "First title","filename":"firstfile.mp3"},
{"title": "Second title","filename":"secondfile.mp3"}});
Listen for the event ended like this:
document.getElementById("audioPlayer").addEventListener("ended", nextSong, false);
And put the script in the <head> element.
var audioPlayer=document.getElementById('audioPlayer');
… will error, since the element doesn't have an id.

Categories

Resources