JavaScript audio not playing outside of jQuery function - javascript

I know the question title doesn't make much sense, but I can't think of a better way to put it. I am a newbie to jQuery and I'm using this code to fade in a <div> and play a sound:
$(document).ready(function(){
$('#speech').fadeIn('medium', function() {
play('msg_appear');
var sptx = $('<p class="stext">').text('There is nothing here.');
$('#speech').append(sptx);
$('.stext').typeOut({marker: '', delay: 22});
});
});
This code runs fine however the sound plays after the fade-in is complete. I wanted it to play while it was fading in, so I tried placing the play() call outside of the fade-in function like this:
$(document).ready(function(){
play('msg_appear');
$('#speech').fadeIn('medium', function() {
However, now it's not playing at all. There's no errors on the JavaScript console so I'm unsure if it's a syntax error, and probably something obvious, but I don't know what.
play() is a function I found to play audio, here it is if it matters at all. I placed it in the same file the above code is; right above the $(document).ready().
function play(sound) {
if (window.HTMLAudioElement) {
var snd = new Audio('');
if(snd.canPlayType('audio/ogg')) {
snd = new Audio(sound + '.ogg');
}
else if(snd.canPlayType('audio/mp3')) {
snd = new Audio(sound + '.mp3');
}
snd.play();
}
else {
alert('HTML5 Audio is not supported by your browser!');
}
}

Based on the comments, I'd recommend modifying the play() function you found:
function play(sound, callback) {
if (window.HTMLAudioElement) {
var snd = new Audio('');
if(snd.canPlayType('audio/ogg')) {
snd = new Audio(sound + '.ogg');
}
else if(snd.canPlayType('audio/mp3')) {
snd = new Audio(sound + '.mp3');
}
if(typeof(callback)=="function"){
snd.addEventListener("canplay", function(){
snd.play();
callback();
});
} else {
snd.addEventListener("canplay", function(){
snd.play();
});
}
snd.load();
}
else {
alert('HTML5 Audio is not supported by your browser!');
}
}
Then call it like:
play('msg_appear', function(){
$('#speech').fadeIn('medium', function() {
var sptx = $('<p class="stext">').text('There is nothing here.');
$('#speech').append(sptx);
$('.stext').typeOut({marker: '', delay: 22});
});
});
That way the audio's play(); won't fire until after the audio can start playing, and the fadeIn() will be started at the same time as the audio.
And of course, the callback isn't necessary; it just makes for more robust timing in varying network conditions.

I can only think that $(document).ready is too early to allow play to happen (in whichever browser is exhibiting this problem), when not delayed by the fadeIn.
Try :
$(window).on('load', function(){
play('msg_appear');
$('#speech').fadeIn('medium', function() {
var sptx = $('<p class="stext">').text('There is nothing here.');
$('#speech').append(sptx);
$('.stext').typeOut({marker: '', delay: 22});
});
});
EDIT:
If necessary, you can introduce further delay as follows:
$(window).on('load', function(){
setTimeout(function() {
play('msg_appear');
$('#speech').fadeIn('medium', function() {
var sptx = $('<p class="stext">').text('There is nothing here.');
$('#speech').append(sptx);
$('.stext').typeOut({marker: '', delay: 22});
});
}, 1000);//adjust up/down to the minimum reliable value
});

First of all, see: HTML5 Audio onLoad
There are some ways to handle loading state. The accepted answer looks weird though.
I recommend code like this:
function makePlayer(sound){
if (window.HTMLAudioElement) {
var snd = new Audio('');
if(snd.canPlayType('audio/ogg')) {
snd = new Audio(sound + '.ogg');
}
else if(snd.canPlayType('audio/mp3')) {
snd = new Audio(sound + '.mp3');
}
return function(){
snd.play();
};
}
};
var play = makePlayer('msg_appear');
$(document).ready(function(){
$('#speech').fadeIn('medium', function() {
play();
var sptx = $('<p class="stext">').text('There is nothing here.');
$('#speech').append(sptx);
$('.stext').typeOut({marker: '', delay: 22});
});
});

Related

How to pass variable asyncronously through different scripts

I can't figure out how to do it.
I have two separate scripts. The first one generates an interval (or a timeout) to run a specified function every x seconds, i.e. reload the page.
The other script contains actions for a button to control (pause/play) this interval.
The pitfall here is that both sides must be asyncronous (both run when the document is loaded).
How could I properly use the interval within the second script?
Here's the jsFiddle: http://jsfiddle.net/hm2d6d6L/4/
And here's the code for a quick view:
var interval;
// main script
(function($){
$(function(){
var reload = function() {
console.log('reloading...');
};
// Create interval here to run reload() every time
});
})(jQuery);
// Another script, available for specific users
(function($){
$(function(){
var $playerButton = $('body').find('button.player'),
$icon = $playerButton.children('i');
buttonAction = function(e){
e.preventDefault();
if ($(this).hasClass('playing')) {
// Pause/clear interval here
$(this).removeClass('playing').addClass('paused');
$icon.removeClass('glyphicon-pause').addClass('glyphicon-play');
}
else {
// Play/recreate interval here
$(this).removeClass('paused').addClass('playing');
$icon.removeClass('glyphicon-play').addClass('glyphicon-pause');
}
},
buttonInit = function() {
$playerButton.on('click', buttonAction);
};
buttonInit();
});
})(jQuery);
You could just create a simple event bus. This is pretty easy to create with jQuery, since you already have it in there:
// somewhere globally accessible (script 1 works fine)
var bus = $({});
// script 1
setInterval(function() {
reload();
bus.trigger('reload');
}, 1000);
// script 2
bus.on('reload', function() {
// there was a reload in script 1, yay!
});
I've found a solution. I'm sure it's not the best one, but it works.
As you pointed out, I eventually needed at least one global variable to act as a join between both scripts, and the use of a closure to overcome asyncronous issues. Note that I manipulate the button within reload, just to remark that sometimes it's not as easy as moving code outside in the global namespace:
Check it out here in jsFiddle: yay! this works!
And here's the code:
var intervalControl;
// main script
(function($){
$(function(){
var $playerButton = $('body').find('button.player'),
reload = function() {
console.log('reloading...');
$playerButton.css('top', parseInt($playerButton.css('top')) + 1);
};
var interval = function(callback) {
var timer,
playing = false;
this.play = function() {
if (! playing) {
timer = setInterval(callback, 2000);
playing = true;
}
};
this.pause = function() {
if (playing) {
clearInterval(timer);
playing = false;
}
};
this.play();
return this;
};
intervalControl = function() {
var timer = interval(reload);
return {
play: function() {
timer.play();
},
pause: function(){
timer.pause();
}
}
}
});
})(jQuery);
// Another script, available for specific users
(function($){
$(function(){
var $playerButton = $('body').find('button.player'),
$icon = $playerButton.children('i'),
interval;
buttonAction = function(e){
e.preventDefault();
if ($(this).hasClass('playing')) {
interval.pause();
$(this).removeClass('playing').addClass('paused');
$icon.removeClass('glyphicon-pause').addClass('glyphicon-play');
}
else {
interval.play();
$(this).removeClass('paused').addClass('playing');
$icon.removeClass('glyphicon-play').addClass('glyphicon-pause');
}
},
buttonInit = function() {
$playerButton.on('click', buttonAction);
interval = intervalControl();
};
buttonInit();
});
})(jQuery);
Any better suggestion is most welcome.

What is the better way to get mouse hold time?

I'm trying to count the time that player is holding the mouse button down. I tried this but it didn't works:
var Game = cc.Layer.extend({
count: false,
countTimer: null,
init: function () {
var selfPointer = this;
this.canvas.addEventListener('mousedown', function(evt) {
selfPointer.count = true;
selfPointer.countTimer = window.setTimeout(selfPointer.Count(), 1);
});
this.canvas.addEventListener('mouseup', function(evt) {
selfPointer.count= false;
window.clearTimeout(selfPointer.countTimer);
});
},
Count: function() {
if (this.count)
{
window.setTimeout(this.Count(), 1);
}
}
This is a part of my code(for brevity) that I want to do an action any 1 milisecond if player is holding the button.
This isn't working besides, I presume that is a better way to do this than mine way. Any ideas?
Why do you use timeouts for this simple task? You can just get time of mousedown, time of mouseup and calculate difference of them. Anyway, timer resolution in browsers is less than 1ms. Read this article of Nickolas Zakas to get more info about time resolution.
Code is:
var Game = cc.Layer.extend({
init: function () {
var holdStart = null,
holdTime = null;
this.canvas.addEventListener('mousedown', function(evt) {
holdStart = Date.now()
});
this.canvas.addEventListener('mouseup', function(evt) {
holdTime = Date.now() - holdStart;
// now in holdTime you have time in milliseconds
});
}
Well if you are targeting newer browsers that are HTML5 compatible you can use web workers for this kind of task. Simply post a message to the web worker on mouse press to start the timer. The web worker can post a message back every 1 ms ish to trigger your in game action and then on mouse release, post another message to the web worker telling it to stop.
Here is just a quick example of how that would work. You would need to run from a local server to have web workers working.
Game.js
function start() {
var worker = new Worker("ActionTrigger.js");
worker.addEventListener('message', function(objEvent) {
console.log("Holding");
});
worker.postMessage("start");
window.onmousedown = function() {
console.log("Mouse press");
worker.postMessage("startTrigger");
}
window.onmouseup = function() {
console.log("Mouse release");
worker.postMessage("endTrigger");
}
}
ActionTrigger.js
var trigger = false;
var interval = 1;
self.addEventListener('message', function(objEvent) {
if(objEvent.data == 'startTrigger')
trigger = true;
else if(objEvent.data == 'endTrigger')
trigger = false;
else if(objEvent.data == 'start')
timerCountdown();
});
function timerCountdown() {
if(trigger)
postMessage("");
setTimeout(timerCountdown,interval);
}
You could use something like this:
http://jsfiddle.net/yE8sh/
//Register interval handle
var timer;
$(document).mousedown(function(){
//Starts interval. Run once every 1 millisecond
timer=self.setInterval(add,1);
});
$(document).mouseup(function(){
//Clears interval on mouseup
timer=window.clearInterval(timer);
});
function add(){
//The function to run
$("body").append("a");
}

Preload all sound and images before starting animation

I'm building a simple animation web app that involves sound and images. I want it so that when a user enters the site they see a "now loading" page, and when all the images and audio files are done loading it calls a function that starts the animation. So far the code I have is roughly this:
var img1, img2;
var sound1, sound2;
function loadImages() {
img1=new Image();
img1.src="...";
img2=new Image();
img2.src="...";
img1.onload=img2.onload=handleImageLoad;
}
function loadSound() {
createjs.Sound.registerSound({id:"sound1", src:"sound1.wav"});
createjs.Sound.registerSound({id:"sound2", src:"sound2.wav"});
createjs.Sound.addEventListener("fileload", handleSoundLoad);
}
function handleImageLoad(e) {
//Do something when an image loads
}
function handleSoundLoad(e) {
//Do something when a sound loads
if(e.id=="sound1") sound1 = createjs.Sound.createInstance("sound1");
else if(e.id=="sound2") sound2 = createjs.Sound.createInstance("sound2");
}
$(document).ready(function() {
//overlay a "now loading" .gif on my canvas
...
...
loadSound();
loadImages();
}
// call this when everything loads
function start() {
//remove "now loading..." overlay .gif
...
...
//start animating
}
handleImageLoad() and handleSoundLoad() are invoked independently of each other so I can't rely on them on calling start(). How can I figure out when everything is loaded? What callback function(s) can I use to achieve this?
Using the built-in SoundJS registerSound method is not bad, but PreloadJS will handle both Sounds and images (and other types) in a single queue. Check out the examples in the PreloadJS GitHub.
http://preloadjs.com and
http://github.com/CreateJS/PreloadJS/
This is probably not the best solution, but I did something similar this way:
var lSnd = false;
var lImg = false;
$(document).ready(function(){
setInterval(checkLoadComplete,1000);
});
function loadSound(){
//loadingSound
//on load complete call= soundLoadingComplite();
}
function soundLoadingComplite(){
//do some stuff
lSnd = true;
}
function loadImages(){
//loadingImages
//on load complete call= imageLoadingComplite();
}
function imageLoadingComplite(){
//do some stuff
lSnd = true;
}
function checkLoadComplete(){
if(lSnd&&lImg){
startAnimation();
//clear interval
}
}
Hope this helps.
I'd be doing it this way:
function loadSound(){
songOK = false;
song = new Audio()
song.src = uri;
song.addEventListener('canplaythrough', function(){ songOK = true; }, false)
return songOK;
}
function loadImage(){
pictOK = false;
pict = new Image();
pict.src = uri;
pict.onload = function (){
pictOK = true;
}
return pictOK;
}
function loadMedia(){
startNowLoading();
if( loadSound() && loadImage())
stopNowLoading();
else
kaboom(); //something went wrong!
}
$(document).ready(function(){
loadMedia();
}
This should allow you to execute the functions in sequence.

Javascript: Setting track position form soundcloud api

I've been trying to play a track from Soundcloud at a specific position.
I have used the code from the following question as a reference
Soundcloud API: how to play only a part of a track?
below is the code that I have used
function playTrack(id) {
SC.whenStreamingReady(function() {
var sound = SC.stream(id);
sound.setPosition(240000); // position, measured in milliseconds
console.log(sound.position)
sound.play()
});
}
The console.log returns 0 and the track plays from the beginning.
alternative code I have tried and had the same result with is below
SC.stream("/tracks/" + id, function(sound){
console.log("playing")
console.log(sound)
sound.setPosition(120000)
console.log(sound.position)
sound.play()
})
Since soundcloud no longer uses soundmanager. the above methods will no longer work.
What must be done is to use a function called "seek()" instead of "setPosition()"
sound.seek(1500);
Here's a better example:
SC.stream(
"/tracks/[trackID]",
function(sound){
sound.seek(1500)
});
And if you wanted to access the seek function outside of the callback I would do this:
var soundTrack;
SC.stream(
"/tracks/[trackID]",
function(sound){
soundTrack = sound;
});
var buttonClickEvent = function(){
soundTrack.seek(1500);
}
According to SoundCloud's docs they are using SoundManager2 to handle playback, so you should be able to use any of its methods or settings. Try this:
function playTrack(id) {
SC.whenStreamingReady(function() {
var sound = SC.stream(id, {
from: 120000, // position to start playback within a sound (msec)
autoLoad: true,
autoPlay: true,
onplay: function(){ console.log(this.position) }
});
});
}
If that doesn't work you can try this instead:
function playTrack(id) {
SC.whenStreamingReady(function() {
var sound = SC.stream(id, { autoPlay: false }, function(sound){
sound.setPosition(240000); // position, measured in milliseconds
console.log(sound.position);
sound.play();
});
sound.load();
});
}
This should work-
var play_sound = function(id) {
SC.whenStreamingReady(function() {
var sound = SC.stream("/tracks/"+id,{autoPlay: false}, function(sound) {
sound.play({
whileplaying: function() {
console.log( this.position );
}
});
});
});
}

quit loop on JS event

so im a little rusty with my JS, but here is my code...
basically i have an image, that on mouseover, it cycles through a hidden div full of other images... fading it out, replacing the src, and fading back in. it works great. but once it gets through all the images, i want it to start back over and keep looping through them until the mouseout event stops it.
i thought i could just call the function again from within the function cycle_images($(current_image));, but that leads to the browser freaking out, understandably. what is a good method to accomplish this?
$.fn.image_cycler = function(options){
// default configuration properties
var defaults = {
fade_delay: 150,
image_duration: 1500,
repeatCycle: true,
};
var options = $.extend(defaults, options);
this.each(function(){
var product = $(this);
var image = $('div.image>a.product_link>img', product);
var description = $('div.image>div.description', product);
var all_images = $('div.all_images', product);
var next_image = ($(all_images).find('img[src="' + $(image).attr('src') + '"]').next('img').attr('src')) ? $(all_images).find('img[src="' + $(image).attr('src') + '"]').next('img') : $(all_images).children('img').first();;
// mouseover
image.fadeOut(options.fade_delay, function(){
image.attr('src', next_image.attr('src'));
image.fadeIn(options.fade_delay);
});
if (options.repeatCycle){
var loop = function() {
product.image_cycler();
}
setTimeout(loop,options.image_duration);
}
});
};
$(document).ready(function() {
$('div.product').hover(function(){
$(this).image_cycler();
}, function(){
$(this).image_cycler({repeatCycle: false});
});
});
It sounds like you want it to re-cycle and stop on mouseout.
After you define the cycle_images() function, add a flag, repeatCycle
cycle_images.repeatCycle = true;
At the end of the cycle_images function, add a recursive call with a timeout
if (this.repeatCycle) {
var f = function() {
return cycle_images.apply(this, [$current_image]);
}
setTimeout(f,50);
}
Then in mouseout,
cycle_images.repeatCycle = false;

Categories