BigVideo.js .on("ended") fires multiple times - javascript

I'm using BigVideo.js jQuery plugin to show full screen background videos. I have links that load different background videos on click. At the end of each video (3-5 sec long) I'm loading an animation. Each video has its own animation on top.
Right now I'm fading in the animations with a time out that corresponds to the video length but it's not full proof. What I really need is for #anim03 to fade in when video-03.mp4 ends. But I can't figure out exactly how the BigVideo.js .on("ended") event really works. In the code I have below (simplified):
// init plugin
var BV = new $.BigVideo({useFlashForFirefox:false});
BV.init();
function setupVideo(url) {
if (Modernizr.touch) {
BV.show(url + '.jpg');
} else {
BV.show(url + '.mp4',{
altSource: url + '.webm',
ambient: false
});
}
}
function setupAnimation(num) {
BV.getPlayer().on("ended", function () { // event from video.js API - when video ends playing
$('#anim0' + num).animate({ opacity: 1 });
});
}
$('a').on('click', function(e) {
e.preventDefault();
// we remove .ext cause we got to setup .webm and .jpg versions
var url = $(this).attr('href').replace('.mp4', '');
setupVideo(url);
var current = $(this).parent().index()+1;
setupAnimation(current);
});
The event is triggered but it seems to go through some sort of queue and fire multiple times. If I console.log num like that:
function setupAnimation(num) {
console.log(num);
BV.getPlayer().on("ended", function () {
$('#anim0' + num).animate({ opacity: 1 });
});
}
I get a single expected value. But if I do it like that:
function setupAnimation(num) {
BV.getPlayer().on("ended", function () {
console.log(num);
$('#anim0' + num).animate({ opacity: 1 });
});
}
Then I get multiple values for num and each time I click and this function is called, I get more and moreā€¦ I'm guessing this .on("ended") loops through some array or something? I can't figure out that part from looking at the plugin's code.
Any help or pointer much appreciated!

When you use the player's "on" function you register a new listener function (This gives you the possibility to trigger multiple functions when something happens).
The reason your on "ended" function fires multiple times, is because each time setupAnimation executed it adds a new instance of the listener-function:
function () {
console.log(num);
$('#anim0' + num).animate({ opacity: 1 });
}
check the API documentation of Video.JS
https://github.com/videojs/video.js/blob/master/docs/api/vjs.Player.md#on-type-fn-
If I get right what you're trying to accomplish, the solution is to run 'setupAnimation' outside the on 'click' function.

Related

How to start javascript setinterval once cleared?

I have a setinterval that runes every 5 seconds. this works fine on page load.
I have the following scenarios:
Load page with interval (WORKS)
press button and load new content and stopp interval(WORKS)
Once the new content is no longer desiered, dissmiss it, return to first content and start interval again(DOES NOT WORK)
I have saftys suchs as events for window.blur that also stops the interval so that the browser does not commponsate for all the missing intervals if i would change tabs or something. Keep in mind that step 3 did not work BUT if i would after step 3 change a tab and then return to my original page(execute blur) the interval would start working again.
NOTE all content loading here exept page load is done with ajax calls.
My code:
initializing:
$.automation.worker.bindIntervalEvent("#TanksContent", "/Tank/GetTanks", function() {
$.automation.tanks.tableInit();
});
binding function:
bindIntervalEvent: function (target, url, callback) {
$(window)
.on("focus.mine",
function() {
$.automation.worker.setUpdateInterval(target, url, callback);
})
.on("blur",
function() {
$.automation.worker.stopUpdateInterval();
}).trigger("focus.mine");
}
interval function:
setUpdateInterval: function (target, url, callback) {
if ($.automation.globals.globalInterval.value.length === 0) {
$.automation.globals.globalInterval.value.push(window.setInterval(
function () {
var options = {
loadTarget: target
}
$.automation.worker.getView(url,
function() {
if (callback)
callback();
},
options);
},
5000));
}
}
the function that stops the interval:
stopUpdateInterval: function () {
if ($.automation.globals.globalInterval.value.length === 0)
return;
console.log("deleting");
for (var i = 0; i <= $.automation.globals.globalInterval.value.length; i++) {
window.clearInterval($.automation.globals.globalInterval.value[i])
$.automation.globals.globalInterval.value.splice(i, 1);
console.log($.automation.globals.globalInterval.value.length);
}
}
when stopping the interval i also remove the window bindings:
unBindIntervalEvent: function() {
$(window).off("focus.mine");
$(window).unbind("blur");
}
Back to step 3:
My sucess method in the callback to my getviewfunction is identical to what i execute in the beginning
code:
$(".updatelatest")
.on("click",
function () {
var _this = $(this);
var options = {
loadTarget:"#TanksContent"
}
$.automation.worker.getView("/Tank/GetTanks",
function (data) {
$(_this).switchClass("col-md-5", "col-md-1", 1000, function() {
$(_this).addClass("hidden");
$(".search").switchClass("col-md-5", "col-md-12", 1000, "easeInOutQuad");
})
$.automation.tanks.tableInit();
$.automation.worker.bindIntervalEvent("#TanksContent", "/Tank/GetTanks", function () {
$.automation.tanks.tableInit();
});
$(window).trigger("blur");
}, options);
});
but this does not start the interval. it is clearly initialized since it works when window.blur is executed for example when I change tab but for some reason this is not working beyond that.
i tried triggering the windows blur event and nothing happened, i tried triggering my custom window event "focuse.mine" but nothing happens.
I did not notice this while developing since I had firebug open and every time i checked scripts or css or the console the blur function was executed so I assumed that my code worked as intended but now that it is deployed I notice this.
My head is pounding beyond reason and I can't for figure out where I have gone wrong.
Well this was a fun one. I simply found that when calling the setUpdateInterval(); function directly it gave me the desiered result.
I realized that the reason I had them split like I did was becaouse of the blur event. "Focus.mine" is triggered to start the inteval again ocne a user comes back to the page.

How to remove the pause between audio tracks in Javascript

I am building a javascript game, and i want to create a background music based on sound file snippets. Short mp3 files to play them as one continuous track. I have tried binding an "ended" event handler on the audio file, though this causes a delay between audio fragments.
To solve this I made a hacky solution that still does not work, changing the audio 1 second before it finishes.
Ebuc.manageAudio = function(){
var listener = function (event) {
if (this.currentTime > (this.duration - 1) && Ebuc.bgnext) {
Ebuc.manageAudio();
console.log("aduio");
Ebuc.bgnext = false;
}
if(this.currentTime < 2){
Ebuc.bgnext = true;
console.log("reset");
}
console.log(event);
console.log("listener active")
};
var color = Level.current.color;
if(Ebuc.bgsong == null) {
Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
Ebuc.bgsong.addEventListener('timeupdate', listener, true);
}
else{
Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
}
Ebuc.bgsong.play();
Resources.audioSetList[color].next();
};
This sample works once, when it is time to switch fragment 2 to fragment 3 the loop stops. Console logging the event listener gives 4 times a log before stopping.
Q1: Why is this eventlistener suddenly disappearing?
Q2: Is there a non hack solution for chaining these audio fragments.
I thank you in advance.
You're going to have more than just pausing issues trying to rapidly switch between two short audio clips, you're going to probably want to crossfade between the two audio tracks quickly as well to prevent any popping, artifacts, etc.
Here's an example of crossfading using howler from howler's github issues. You could probably use this example, and keep a queue of loaded instances to transition to. I hope that helps.
//you'll probably want this crossfade duration to be shorter.
var crossfadeDuration = 5000,
volume = 0.7;
var instance1, instance2, soundDuration;
// Singleton helper to build similar instances
var createHowlerInstance = function (urls, onload) {
return new Howl({
urls: urls,
loop: false,
volume: 0,
onload: onload
});
};
// Create "slave" instance. This instance is meant
// to be played after the first one is done.
instance2 = createHowlerInstance(['file2.mp3']);
// Create "master" instance. The onload function passed to
// the singleton creator will coordinate the crossfaded loop
instance1 = createHowlerInstance(['file1.mp3'], function(){
// Get the sound duration in ms from the Howler engine
soundDuration = Math.floor(instance1._duration * 1000);
(function crossfadedLoop(enteringInstance, leavingInstance){
// Fade in entering instance
enteringInstance.pos(0).play().fade(0, volume, crossfadeDuration);
// Wait for the audio end to fade out entering instance
// white fading in leaving instance
setTimeout(function(){
enteringInstance.fade(volume, 0, crossfadeDuration);
crossfadedLoop(leavingInstance, enteringInstance);
}, soundDuration - crossfadeDuration);
})(instance1, instance2);
});
By using the idea of setting a timeOut in the answer of pantalohnes I have created the following code to solve the gap:
Ebuc.manageAudio = function(){
var color = Level.current.color;
Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
Ebuc.bgsong.addEventListener("loadedmetadata",function(){
setTimeout(Ebuc.manageAudio, (Ebuc.bgsong.duration * 1000) - 50);
Ebuc.bgsong.play();
console.log(Ebuc.bgsong.duration);
Resources.audioSetList[color].next();
});
};
The 50 milliseconds timeout bridges the gap between the sequenced files exactly.
Answering your question (although I see you found another solution), I think I found your bug:
The second time your enter Ebuc.manageAudio(), Ebuc.bgsong is already set and you just create a new Audio Ebuc.bgsong = new Audio(...) without attaching the listener to it, so you're not being notified for any 'timeupdate' events emitted when playing the second audio file.
You should also remove the listener from the previous playing audio.
So, if all else is ok, I think this should fix it:
Ebuc.manageAudio = function(){
var listener = function (event) {
if (this.currentTime > (this.duration - 1) && Ebuc.bgnext) {
Ebuc.manageAudio();
console.log("aduio");
Ebuc.bgnext = false;
}
if(this.currentTime < 2){
Ebuc.bgnext = true;
console.log("reset");
}
console.log(event);
console.log("listener active")
};
var color = Level.current.color;
if(Ebuc.bgsong != null) {
Ebuc.bgsong.removeEventListener('timeupdate', listener, true);
}
Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
Ebuc.bgsong.addEventListener('timeupdate', listener, true);
Ebuc.bgsong.play();
Resources.audioSetList[color].next();
};
More than that, I think that if you properly remove the listener from the previous playing audio, you won't need that bgnext hack at all:
var listener = function (event) {
if (this.currentTime > (this.duration - 1)) {
Ebuc.manageAudio();
console.log("aduio");
}
console.log(event);
console.log("listener active")
};
Ebuc.manageAudio = function () {
var color = Level.current.color;
if (Ebuc.bgsong != null) {
Ebuc.bgsong.removeEventListener('timeupdate', listener, true);
}
Ebuc.bgsong = new Audio('assets/sound/' + Resources.audioSetList[color].getcurrentsong());
Ebuc.bgsong.addEventListener('timeupdate', listener, true);
Ebuc.bgsong.play();
Resources.audioSetList[color].next();
};
Let me know if that worked :)

how can I rearrange this code so my setInterval stops looping infinitely?

I'm trying to make a simple flip-card/memory match (like from super mario brothers 3) game in HTML/Javascript and am having a slight issue with the setInterval command.
Here is a link to the full code: http://jsfiddle.net/msfZj/
Here is the main issue/main logic of it:
if(click == 2) //denotes two cards being clicked
{
if(flippedArray[1].src === flippedArray[0].src) // if click 1 == click 2 then refer to function 'delayMatch' which sets click 1 and 2 cards to not be displayed
{
window.setInterval(function() { delayMatch() }, 500);
console.log("EQUAL");
}
else
{
window.setInterval(function() { delayNoMatch() }, 500); // if click 1 != click 2 then display card.png
console.log("NOT EQUAL");
}
function delayMatch() //function for matching pairs
{
flippedArray[0].style = "display:none;";
flippedArray[1].style = "display:none;";
}
function delayNoMatch() //function for non-matching pairs
{
flippedArray[0].src = "card.png";
flippedArray[1].src = "card.png";
}
click = 0; // when clicked two cards set click back to zero
}
The first two cards I click on always work: but from that point onward the setInterval keeps running the function over and over again in an endless loop every 500ms.
I'd be extremely appreciative if anybody can point my in the right direction on how I can do this properly.
Thank you very much for your time.
It looks like you need setTimeout, which only runs once?
window.setTimeout(function() { delayMatch() }, 500);
Otherwise, you need to clear the interval with clearInterval(i), but first set "i" using the return value of setInterval:
var i = window.setInterval(function() { delayMatch() }, 500);
Here's a demo (I JQuerified it a bit for JSFiddle).
You're going to want to go with setTimeout() instead of setInterval()
A handy function when you use setTimeout is clearTimeout. Say you want to set a timer, but maybe want a button to cancel
var timer = setTimeout(fn,1000);
//later maybe
clearTimeout(timer);

Pausable typewriter effect in jQuery

I'm using the typewriter plugin from Jason Frame's Grab Bag.
However, I now find myself in a situation where I need to be able to pause and resume this typewriter at any point during its run.
Ideally, I'd have .typewriter("pause") to pause it, and .typewriter("resume") to resume the animation from where it left off.
I've tried setting a paused variable within the plugin, and leaving the interval running with a check for the variable each iteration -- if true, do nothing.
However, due to my lack of skills in making jQuery plug-ins, each time I call the function to pause the typewriter, it just starts all over again. I suspect the paused variable gets reset.
How would I go about making this plugin pausable?
jsBin demo
Add this 2 lines here:
(function($) {
$.fn.typewriter = function() {
this.each(function() {
var $ele = $(this), str = $ele.text(), progress = 0;
$ele.text('');
timer = setInterval(function() {
if(pause===false){ // ADD THIS LINE ////////////////////////
$ele.text(str.substring(0, progress++) + (progress & 1 ? '_' : ''));
if (progress >= str.length) clearInterval(timer);
} // ADD THIS LINE ////////////////////////
}, 100);
});
return this;
};
})(jQuery);
$("#typewriter").typewriter(); // run your plugin
and I created this:
var pause = false;
$('#play_pause').click(function(){
pause= !pause ? true : false; // toggle true/false pause states
});
EDIT: find a better plugin-like solution in the comment below!

YouTube Javascript API onstateChange event problems

My javascript:
function onYouTubePlayerReady()
{
playerObj = document.getElementById("player_embed");
playerObj.addEventListener("onStateChange", test);
// Make sure the document is loaded before any DOM-manipulation
$(document).ready(function() {
// Update player information every 500ms
setInterval(updatePlayerInfo, 500);
});
}
function test(newState)
{
alert(newState);
}
My videos are loaded correctly and I can control fine them through my javascript. However the onStateChange event never seems to trigger. It never comes up with an alert when I'm playing/stopping/pausing videos etc. Anyone with a solution/similar problem?
You must pass the function name as a string to addEventListener.
playerObj.addEventListener("onStateChange", "test");
This format is for JQuery but the logic is same, create a function inside onYouTubePlayerReady to pass the playerId
$(document).ready(function() {
onYouTubePlayerReady = function(playerId) {
eventHandler=function(state){onStateChangeID(state,playerId);}
playerObj = document.getElementById("player_embed");
playerObj.addEventListener('onStateChange','eventHandler',false);
}
onStateChangeID=function(newState,playerId) {
console.log("onStateChange: ("+playerId+") " + newState);
}
});

Categories