I'm creating a Simon game for FreeCodeCamp and I'm trying to use a for loop to play an audio sound for each piece of the pattern.
The code I have now plays the audio all at once which won't work for the game. How can I change this code so that it will play each audio sound individually with a .5 second break in between?
Here's the function that plays the audio and adds visual effect
function playAudio() {
if (colorColor === "red") {
red.play();
$('#red').effect("highlight", { color: colorColor }, 500);
} else if (colorColor === "blue") {
blue.play();
$('#blue').effect("highlight", { color: colorColor }, 500);
} else if (colorColor === "green") {
green.play();
$('#green').effect("highlight", { color: colorColor }, 500);
} else if (colorColor === "yellow") {
yellow.play();
$('#yellow').effect("highlight", { color: colorColor }, 500);
}
}
This is the function where I believe the issue is.
function playPattern() {
for (var i = 0; i < pattern.length; i++) {
colorColor = pattern[i];
setTimeout(playAudio, 500);
}
setTimeout(random, 750);
}
And here's the random() function only because it is called within playPattern()
function random() {
randomColor = colors[Math.floor(Math.random() * colors.length)];
colorColor = randomColor;
colorColor = colorColor.slice(1);
pattern.push(colorColor);
count++;
if (count < 10) {
document.getElementById("count").innerHTML = "0" + count;
} else {
document.getElementById("count").innerHTML = count;
}
playAudio();
pickCount = 0;
userPick = [];
}
Thanks!
Assuming your audios are all the same length:
var audioLength = 3000; // length of audio in ms
for (var i = 0; i < pattern.length; i++) {
colorColor = pattern[i];
setTimeout(playAudio, (i * (audioLength + 500));
}
In your code where you are doing:
for (var i = 0; i < pattern.length; i++) {
colorColor = pattern[i];
setTimeout(playAudio, 500);
}
What happens is that you basically define to run playAudio in 500ms for all patterns. the for loop is processed immediately and 500ms later all audios are being played together.
What you want is to call the next timeout only when the playAudio function ends.
let i = 0;
function playAudio() {
i++;
colorColor = pattern[i];
if (colorColor === "red") {
red.play();
$('#red').effect("highlight", { color: colorColor }, 500);
} else if (colorColor === "blue") {
blue.play();
$('#blue').effect("highlight", { color: colorColor }, 500);
} else if (colorColor === "green") {
green.play();
$('#green').effect("highlight", { color: colorColor }, 500);
} else if (colorColor === "yellow") {
yellow.play();
$('#yellow').effect("highlight", { color: colorColor }, 500);
}
if (i < pattern.length) {
setTimeout(playAudio, 500);
}
}
function random() {
randomColor = colors[Math.floor(Math.random() * colors.length)];
colorColor = randomColor;
colorColor = colorColor.slice(1);
pattern.push(colorColor);
count++;
if (count < 10) {
document.getElementById("count").innerHTML = "0" + count;
} else {
document.getElementById("count").innerHTML = count;
}
playAudio();
pickCount = 0;
userPick = [];
}
setTimeout(random, 750);
I based my answer on this answer.
What I did was to preload audio files by creating an object with the titles as keys and with an object value which contains the files' source and if it's loaded already:
const notes = ['do', 're', 'mi', 'fa', 'sol', 'la', 'ti'];
const audioFiles = notes.reduce((obj, title) => {
obj[title] = {
src: `notes/${title}.wav`,
loaded: false,
};
return obj;
}, {});
// Preload files.
Object.entries(audioFiles).forEach(([title, details]) => {
const audio = new Audio();
audio.addEventListener('canplaythrough', () => {
details.loaded = true;
});
audio.src = details.src;
});
The playPlaylist function takes a playlist (array of titles) and an object containing the audio files:
const playAudio = function playAudio(player, src) {
player.src = src;
player.play();
};
const playPlaylist = function playPlaylist(playlist, files) {
// TODO: Check if files in playlist are already loaded.
// while (!filesLoaded) { ... }
const player = new Audio();
let audioIndex = 0;
player.addEventListener('ended', () => {
audioIndex++;
if (audioIndex >= playlist.length) {
return;
}
setTimeout(() => {
playAudio(player, files[playlist[audioIndex]].src);
}, 500);
});
playAudio(player, files[playlist[audioIndex]].src);
};
Then just provide the playlist and pass the audioFiles object.
// Play sample playlist.
const playlist = 'do,mi,fa,sol,do,re,la,fa,mi'.split(',');
playPlaylist(playlist, audioFiles);
Just change the files and file paths to test.
You need to consider how much the song lasts, and include that on your timeout, for example if red lasts 1 min, you need to set your next timeout to setTimeout(blue, 60*1000 + 500), something like this :
function playAudio(color) {
if (colorColor === "red") {
red.play();
$('#red').effect("highlight", { color: colorColor }, 500);
setTimeout(function () {
playAudio("blue");
},60*1000 + 500);
} else if (colorColor === "blue") {
blue.play();
$('#blue').effect("highlight", { color: colorColor }, 500);
setTimeout(function () {
playAudio("green");
},60*1000 + 500);
} else if (colorColor === "green") {
//.... ETC
You don't need a for, just some recursion
Related
I'm trying to play an audio file in js. what am i doing wrong?
The script auto-stops after some events which is working 100%, now all I want to do is add a sound after it stops
Here is the code in:
function stop() {
stop_flag = true;
var audio = new Audio('play.mp3');
audio.play();
}
Here is the complete script.js the function is at the bottom.
chrome.runtime.onMessage.addListener(function(message) {
if (message.action == "init") {
init(message.options.divider, message.options.delay, message.options.maxLosses);
}
if (message.action == "stop") {
stop();
}
});
var stop_flag;
function init(divider, delay, maxLosses) {
var $table = $('table.dice_table').last();
var lastBetNumber;
var lastBetValue;
var lastProfit;
var losses = 0;
stop_flag = false;
loop_start();
function loop_start() {
lastBetNumber = getLastBetNumber();
var betValue = (getCurrentBalance() / divider).toFixed(9);
//var betValue = (getCurrentBalance() / divider);
lastBetValue = betValue;
$("input.bet").val(betValue);
play();
setTimeout(loop, delay);
}
function loop() {
if (stop_flag)
return;
waitForTableChange()
.then(roll)
.catch(loop_start);
function roll() {
//only if the last bet has changed \/
lastBetNumber = getLastBetNumber();
lastProfit = $table.children('tbody').children('tr').first().children('td').last().text()[0];
changeBet();
if (losses >= maxLosses)
return;
play();
setTimeout(loop, delay);
}
}
function waitForTableChange() {
return new Promise(function(resolve, reject) {
var n = 0;
attempt();
function attempt() {
n++;
if (n >= 100) {
reject();
return;
}
if (getLastBetNumber() == lastBetNumber) {
setTimeout(attempt, 100);
return;
}
resolve();
}
});
}
function changeBet() {
var newBetValue;
if (lastProfit == "-") {
losses++;
newBetValue = lastBetValue * 2;
} else {
losses = 0;
newBetValue = (getCurrentBalance() / divider).toFixed(9);
}
lastBetValue = newBetValue;
$("input.bet").val(newBetValue);
}
function play() {
$('input.clDicePlay').first()[0].click();
}
function getLastBetNumber() {
return $table.children('tbody').children('tr').first().children('td').first().text();
}
function getCurrentBalance() {
return $('.dice_select .chosen-single span').text().split('- ')[1].split(' ')[0];
}
}
function stop() {
var audio = new Audio('play.mp3');
stop_flag = true;
audio.play();
}
The problem is you are setting audio within the function stop(), which makes it local to that function. Once a function has been executed, all local vars are destroyed. You need to use the global scope to keep the object alive.
For example:
// set 'audio' in the global scope
var audio = new Audio('/path/to/default.mp3');
// create a play / pause button
function playPause(e) {
// NOTE: audio is from the gloabl scope
if (this.textContent == 'Play') {
audio.play();
this.textContent = 'Pause';
} else {
audio.pause();
this.textContent = 'Play';
}
}
window.onload = function() {
var a = document.getElementById('func');
a.addEventListener('click',playPause,false);
}
<button id="func">Play</button>
I have written simple image slider using JavaScript. In that I have set 5000 ms timer to slide automatically. but it is getting increased when we click on next or previous button. I have pasted the code below
HTML Code:
<section id="intro">
<div id="prev"><br/><br/><br/><br/><img onclick="rotateImages(0);" src="./images/slider/prev1.png"/></div>
<div id="next"><img onclick="rotateImages(1);" src="./images/slider/next1.png"/></div>
</section>
JavaScript Code:
<script type="text/javascript">
var num = 0;
var temp = 0;
var speed = 5000;
var preloads = [];
preload(
'1.png',
'2.jpg',
'3.jpg',
'4.jpg',
'5.png'
);
function preload() {
for (var c = 0; c < arguments.length; c++) {
preloads[preloads.length] = new Image();
preloads[preloads.length - 1].src = arguments[c];
}
}
function rotateImages(flag) {
if (flag == 0)
{
num = num - 1;
if (num < 0)
num = preloads.length;
}
else
{
num = num + 1;
if (num >= preloads.length)
num = 0;
}
if (num == temp) {
rotateImages(1);
} else {
var str = preloads[num].src;
var resArr = str.split("/");
var picName = resArr[resArr.length - 1];
document.getElementById("intro").style.backgroundImage = 'url(./images/slider/' + picName + ')';
temp = num;
setTimeout(function () {
rotateImages(1)
}, speed);
}
}
if (window.addEventListener) {
window.addEventListener('load', function () {
setTimeout(function () {
rotateImages(1)
}, speed)
}, false);
} else {
if (window.attachEvent) {
window.attachEvent('onload', function () {
setTimeout(function () {
rotateImages(1)
}, speed)
});
}
}
</script>
You need to clear the previous timer. Otherwise there will be new timers added with every call to rotateImages(), Save the reference returned from setTimeout() and then use clearTimeout() to clear the timer if it's set
var num = 0;
var temp = 0;
var speed = 5000;
var preloads = [];
var timeout;
preload(
'1.png',
'2.jpg',
'3.jpg',
'4.jpg',
'5.png'
);
function preload() {
for (var c = 0; c < arguments.length; c++) {
preloads[preloads.length] = new Image();
preloads[preloads.length - 1].src = arguments[c];
}
}
function rotateImages(flag) {
if (flag == 0) {
num = num - 1;
if (num < 0)
num = preloads.length;
} else {
num = num + 1;
if (num >= preloads.length)
num = 0;
}
if (num == temp) {
rotateImages(1);
} else {
var str = preloads[num].src;
var resArr = str.split("/");
var picName = resArr[resArr.length - 1];
document.getElementById("intro").style.backgroundImage = 'url(./images/slider/' + picName + ')';
temp = num;
if (!!timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(function() {
rotateImages(1);
}, speed);
}
}
if (window.addEventListener) {
window.addEventListener('load', function() {
setTimeout(function() {
rotateImages(1)
}, speed)
}, false);
} else {
if (window.attachEvent) {
window.attachEvent('onload', function() {
setTimeout(function() {
rotateImages(1)
}, speed)
});
}
}
This is a simple script for horizontal fortune roulette, created using slick slider. Problem: function rollout() executed twice (and all helpers too) because template rendered twice. I'm using iron:router for routing, code:
Router.configure({
loadingTemplate: "loading",
layoutTemplate: "layout"
});
Router.map(function() {
this.route("hello", {
path: "/",
waitOn: function() {
return Meteor.subscribe("games");
}
});
});
hello.js:
Session.setDefault('currentSlide', 0);
Template.hello.events({
'click .create': function(e) {
e.preventDefault();
var ins = {
gameStartTime: new Date(),
finished: false
}
Games.insert({
gameStartTime: new Date(),
finished: "false"
});
Session.set("gameStatus", "waiting");
}
})
Template.hello.helpers({
currentSlide: function() {
var value = Session.get('currentSlide');
if (value === 25) {
return 0;
}
return value;
},
games: function() {
var game = Games.find();
return game;
},
gameHandler: function() {
var game = Games.findOne();
var gameStatus = Session.get("gameStatus");
if (game && game.finished === "false" && gameStatus === "waiting") {
Games.update({_id: game._id}, {
$set: {running: true}
});
} else if (gameStatus === "finished") {
Meteor.call('updateGame', game._id); //remove item from collection after game over; Error here: TypeError: Cannot read property '_id' of undefined;
console.log('game finished');
}
if (game && game.running === true) {
//Set Session variable and launch roulette
Session.set("gameStatus", "running");
rollout(game._id);
}
}
});
var rollout = function(gameid) {
Session.set("gameStatus", "running");
//make div's visible. They are have display:none by default.
document.getElementById("roulette").style.display = "block";
document.getElementById("result").style.display = "block";
document.getElementById("wrapper").style.display = "none";
var speed = 500; //default slider speed
Session.setDefault('speed', speed);
var r = $('#roulette').slick({
centerMode: true,
slidesToShow: 7,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 0,
speed: speed,
draggable: false,
pauseOnHover: false,
cssEase:'linear'
});
var maximum = 14;
var minimum = 0;
var randomnumber = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
var winResult = randomnumber;
var speed = 80;
var currentSlide = Session.get('currentSlide');
function roll(callback) {
Session.set('currentSlide', r.slickCurrentSlide());
if (speed < 800) {
speed += 20;
r.slickSetOption("speed", speed, false);
setTimeout(function() {
roll(callback);
}, 500);
} else if (speed >= 600 && speed < 1300) {
speed += 40;
r.slickSetOption("speed", speed, false);
setTimeout(function() {
roll(callback);
}, 300);
} else if (speed >= 1300 && speed < 20000) {
setTimeout(function() {
speed += 3;
r.slickSetOption('speed', speed, false);
if (Session.get('currentSlide') === winResult) {
r.slickPause();
setTimeout(function() {
//hide roulette div's
document.getElementById("roulette").style.display = "none";
document.getElementById("result").style.display = "none";
document.getElementById("wrapper").style.display = "block";
r.unslick();
Session.set('currentSlide', 0);
Session.set("gameStatus", "finished");
callback(true);
return
}, 10000);
} else {
roll(callback);
}
}, 30)
} else {
callback(true);
}
}
roll(function(callback) {
Meteor.call('updateGame', gameid); //remove item from collection
Session.set("gameStatus", "finished");
});
}
In template i'm using spinner by sacha:spin package and it working fine.
The gameHandler function should not be in your template helpers, which are only meant to supply information to the template. Instead, that logic needs to be moved to an autorun function. This autorun statement will re-run anytime a reactive datasource within it changes.
ex:
Template.hello.onCreated(function() {
this.autorun(function() {
var game = Games.findOne();
var gameStatus = Session.get("gameStatus");
// ...etc
});
}
This is my interpretation of a custom Soundcloud player using JavaScript SDK. I would like to reset playlist after last track is finished, which is partially acheved - 1st track is properly set, but my counter variable for next/prev buttons doesn't update.
Or maybe there is a better way to loop through the playlist than having a counter variable?
var autoplay = false;
$(document).ready(function() {
SC.get( '/resolve', {url: 'https://soundcloud.com/rockcurrent/sets/rc-playlist-new-rock-releases'}, function(pl) {
var i = 0;
var track_url = pl.tracks[i].uri;
var stream_sound = function(i) {
track_url = pl.tracks[i].uri;
SC.stream(track_url, function(sound) {
var new_play = function() {
sound.play({
onplay: function() {
title(i);
},
whileplaying: function() {
loader(this.position, this.duration);
},
onfinish: function() {
if (i < pl.tracks.length -1) {
i++;
autoplay = true;
}
else {
i = 0;
}
stream_sound(i);
}
});
}
new_play();
sound.pause();
if (autoplay) {
sound.resume();
is_playing = true;
}
autoplay = false;
$('#play').on('click', function() {
togglePause();
});
});
}
stream_sound(i);
$('#next').on('click', function() {
soundManager.stopAll();
if (i < pl.tracks.length -1) {
i++;
}
autoplay = true;
stream_sound(i);
});
$('#prev').on('click', function() {
soundManager.stopAll();
if (i > 0) {
i--;
}
autoplay = true;
stream_sound(i);
});
});
});
Below is some code that has no jquery, but it is not working:
function start() {
var img = 0,
pic = ['nature', 'grass', 'earth', 'fall2', 'waterfall'],
slider = document.getElementsByClassName('slide_img'),
timerID = 0,
delay = 4000;
function back() {
img--;
if (img <= 0) {
img = pic.length - 1;
}
slider.src = 'pictures/' + pic[img] + '.jpg';
}
function go() {
img++;
if (img >= pic.length) {
img = 0;
}
slider.src = 'pictures/' + pic[img] + '.jpg';
}
document.getElementById('back').onclick = function() {
back();
}
document.getElementById('go').onclick = function() {
go();
}
slider.onmouseenter = function() {
clearTimeout(timerID);
}
document.getElementById('go').onmouseenter = function() {
clearTimeout(timerID);
}
document.getElementById('back').onmouseenter = function() {
clearTimeout(timerID);
}
slider.onmouseleave = function() {
clearTimeout(timerID);
auto();
}
function auto() {
timerID = setTimeout(function () {
go();
auto();
}, delay);
}
auto();
}
Here is what it is in jquery, this one works:
$('document').ready(function() {
var img = 0,
pic = ['nature', 'grass', 'earth', 'fall2', 'waterfall'],
slider = $('img.slide_img'),
timerID = 0,
delay = 4000;
function back() {
img--;
if (img <= 0) {
img = pic.length - 1;
}
slider.attr('src', 'pictures/' + pic[img] + '.jpg');
}
function go() {
img++;
if (img >= pic.length) {
img = 0;
}
slider.attr('src', 'pictures/' + pic[img] + '.jpg');
}
$('button#back').on('click', function() {
back();
});
$('button#go').on('click', function() {
go();
});
$(slider).on('mouseenter', function () {
clearTimeout(timerID);
});
$('button#go').on('mouseenter', function () {
clearTimeout(timerID);
});
$('button#back').on('mouseenter', function () {
clearTimeout(timerID);
});
$(slider).on('mouseleave', function () {
clearTimeout(timerID);
auto();
});
function auto() {
timerID = setTimeout(function () {
go();
auto();
}, delay);
}
auto();
});
What am i doing wrong, why is the first one not working. Im am trying to get rid of jquery so i dont have to include the source file.
You are not executing onload
try
var img = 0,
pic = ['nature', 'grass', 'earth', 'fall2', 'waterfall'],
slider,
timerID = 0,
delay = 4000;
window.onload=function() {
slider = document.getElementsByClassName('slide_img');
document.getElementById('back').onclick = function() {
back();
}
document.getElementById('go').onclick = function() {
go();
}
slider.onmouseover = function() {
clearTimeout(timerID);
}
document.getElementById('go').onmouseover = function() {
clearTimeout(timerID);
}
document.getElementById('back').onmouseover = function() {
clearTimeout(timerID);
}
slider.onmouseout = function() {
clearTimeout(timerID);
auto();
}
auto();
}
function auto() {
timerID = setTimeout(function () {
go();
auto();
}, delay);
}
function back() {
img--;
if (img <= 0) {
img = pic.length - 1;
}
slider.src = 'pictures/' + pic[img] + '.jpg';
}
function go() {
img++;
if (img >= pic.length) {
img = 0;
}
slider.src = 'pictures/' + pic[img] + '.jpg';
}
Use "onmouseover" and onmouseout" for canonical events:
slider.onmouseover = function() {
clearTimeout(timerID);
}
document.getElementById('go').onmouseover = function() {
clearTimeout(timerID);
}
document.getElementById('back').onmouseover = function() {
clearTimeout(timerID);
}
slider.onmouseout = function() {
clearTimeout(timerID);
auto();
}
And don't forget to define "slider" (the initial part is missing in your script):
var slider = document.querySelector('img.slide_img');
<body onload="start"></body>
The JavaScript start doesn't do anything. If you want to call a function, then you need to follow it with ().
This is a rather obtrusive way to deal with event handler assignment though. It is generally preferred to assign such things with JavaScript:
addEventListener('load', start);
See MDN for compatibility notes.
However, you don't have anything in your code that depends on the entire document being loaded. So you can simply:
<script>
start();
</script>
</body>
at the end of your document.
(You can put the whole script there for that matter, and keep it in an external file).