Memory Efficient Code with WebAudio API - html

I am developing an HTML5 game and using Web Audio API for sounds. I am having an issue in which sounds start slowing down as game progress and game also starts feeling jerks which i guess is due to java-script GC doing memory cleanup.There are two types of sound i am playing in the game:
1) Background sound which continuously loop
2) Jump sound, hit sound etc occurs due to some event in the game which occurs very frequently. For example: firing multiple bullets from gun.
Not sure what i am doing wrong, please help. Please refer below code
function play(){
this.startTime = this.actx.currentTime;
this.soundNode = this.actx.createBufferSource();
this.soundNode.buffer = this.buffer;
this.soundNode.connect(this.volumeNode);
//If there's no reverb, bypass the convolverNode
if (this.reverb === false) {
this.volumeNode.connect(this.panNode);
}
//If there is reverb, connect the `convolverNode` and apply
//the impulse response
else {
this.volumeNode.connect(this.convolverNode);
this.convolverNode.connect(this.panNode);
this.convolverNode.buffer = this.reverbImpulse;
}
this.panNode.connect(this.actx.destination);
this.soundNode.loop = this.loop;
this.soundNode.playbackRate.value = this.playbackRate;
this.soundNode.start(
this.startTime,
this.startOffset % this.buffer.duration
);
this.playing = true;
}

There's nothing in your code that stands out as specifically memory intensive, apart from the use of the convolver (which can be really expensive and cause bad performance on lower-end devices). I'd try this though:
Try disabling your audio (don't run any of the audio code, don't just mute it). Do you still have the janks in the game visuals? If so, it's not your audio that's the culprit.
Try running your audio but always run it without the convolver. If the jank disappears, the convolver is your culprit. The only thing I could think of there is to try setting the convolver buffer only once and not every call to play().
Try running different profiles in Chrome Dev Tools (JS, Memory, Paints etc.) and try to figure out where the janks come from. https://developer.chrome.com/devtools/docs/cpu-profiling
Good luck!

Related

Phaser sound stops working only on Chrome for Android

The sound works for the first few minutes but after a while .play() doesn't play any sound. It seems like if there is a huge duration where no sound has played the sound stops working.
It works fine on Desktop, iOS and the generic android browser. I'm just running into this problem specifically on android devices using mobile chrome as the browser.
This turns out to be a Chrome bug that causes web audio to stop playing sounds if no sound has played for something like 30 seconds.
https://code.google.com/p/chromium/issues/detail?id=518863
The fix seems to be to watch
audioContext.currentTime
and when it gets stuck after 30 secs create a new audioContext.
The solution I ended up using is as follows:
Note I am using the phaser library - so this exact solution wont work for you - but it will give you the general idea
//This is run using a timer event every second
//this.game.time.events.loop(1000, this.checkAudioContext, this);
evil.AudioManager.prototype.checkAudioContext=function(){
//work out when the audio context has stopped
if(this.game.sound.context.currentTime-this.last_context_time===0){
//close out the existing context and create a new one
//you will also need new gain nodes if you are using them
this.game.sound.context.close();
this.game.sound.context=new AudioContext();
this.game.sound.masterGain= this.game.sound.context.createGain();
this.game.sound.masterGain.gain.volume=this.volume;
this.game.sound.masterGain.connect(this.game.sound.context.destination);
//now go through every sound and connect them to the new context
//creating gain nodes as we go.
for(var key in this.tracks){
var snd=this.tracks[key].snd;
snd.context=this.game.sound.context;
snd.masterGainNode = this.game.sound.masterGain;
snd.gainNode=this.game.sound.context.createGain();
snd.gainNode.gain.value = snd.volume * this.volume;
snd.gainNode.connect(snd.masterGainNode);
}
}else{
//update out time variable
this.last_context_time=ctx.currentTime;
}
}
What version of phaser and android do you have? For me it seems to play without issues for a 4-minute song i tried. Even if the screen goes of, it continues without any problem when it is resumed. A dirty solution will be to add an loop that plays a silent sound every 2 minutes for example just to "refresh" the sound manager if that solves your problem.

How to know if a music file has finished playing?

In cocos2d-x, you can play music by doing the following:
auto audio = SimpleAudioEngine::getInstance();
// set the background music and play it just once.
audio->playBackgroundMusic("mymusic.mp3", false);
But how do we know if the music has finished playing ? The game could be interrupted by a phone call (triggering other code that pauses the game and the music) or paused by the user.
Is there anyway to know if the file has finished playing ? I know we can keep some kind of timer to keep track of the duration the song has been playing but that seems hacky, and this is a common use case, especially if we want to queue a playlist.
It looks like you'll have to test audio->isBackgroundMusicPlaying() in your update() methods.
There ought to be a delegate, or some other kind of callback, which will tell you such things. I guess you could subclass SimpleAudioEngine to add this.
EDIT There is experimental::AudioEngine which offers a didFinishCallback:
static void setFinishCallback(int audioID, const std::function<void(int, const std::string &)> &callback);
However you need to be aware that I don't think it supports all platforms, so use with caution.

FLVPlayback VideoEvent.COMPLETE triggers before the video reaches the end, and the video stops playing

import fl.video.*;
var player:FLVPlayback = new FLVPlayback();
addChild(player);
player.skin = "someSkin.swf";
player.source = "http://someDomian/some.flv";
Here's the code. I just load and play some flv from server, and when I test it via Flash (just press ctrl+enter), the video will stop at about 90% of the video and dispatch the COMPLETE event. Any one knows why? Try it again, the time where the video stops is the same, just about 90%.
I searched it, and I tried to change playheadUpdateInterval of player, set totalTime explictly, or set bufferTime longer. All those solutions make no difference.
I tried to load another movie from the same server, same problem. Any chances something wrong with the server?
EDIT:
I sent the swf to someone else, and the video plays as it's supposed to do. Then another guy, and the same result. So, I guess there's something wrong with my settings? Is there some setting will cause this strange action? But when I tried it on my laptop, the video stops unexpectedly. Why this seems to work on everyone's computer except mine?
before CLOSE:
Eventually my colleagues found the strange reason causes this problem, if changing the publish setting to FP10 & 10.1 or FP9, it'll work as we expect. FP10.2 and up, videos on that server seem not have the talent to find their ending. The videos are H.264/AAC, if I convert them to VP60/MP3, all the FP versions will work. Since I can't modify the setting about the codecs, I'll just change the publish setting and leave the codecs thing alone. Although I'm just frustrated by this, if you see this and so lucky that you happen to know the codecs thing why the "early complete" appears, please please tell me.
The problem could be with the .FLV file, sometimes there could be problems while encoding any video file(AVI,MOV,MP4,3GP,WMV) in to .FLV format that results in skipped frames. Try your code with some other .FLV file if that works good then you need to re-encode your FLV file.
You can also try capturing the video Complete Event by tracking the Video Playback Progress.
import fl.video.*;
var player:FLVPlayback = new FLVPlayback();
addChild(player);
player.skin = "someSkin.swf";
player.source = "http://someDomian/some.flv";
addEventListener(Event.ENTER_FRAME, enterFrameHandler);
function enterFrameHandler(event:Event):void {
if(player.playheadPercentage>=99)
{
//On Video Playback Complete Actions here
}
}

Pausing issue with flash webcam recording

I'm building a webcam recording app in CS5 and I'm having some seemingly random issues with the recorded flv. Currently I'm publishing a stream to Wowza Media Server using the standard _netstream.publish("movieName", "record") command. Everything regarding this works fine and I can play the file back but sometimes there's a 3 to 4 second pause at the beginning or end of the video. There will be a still frame and the time will sit at 0 and then snap to 4. I've explored bandwidth options and I've turned the resolution and quality down considerably and it doesn't seem to have any effect and the rest of the video will play back smoothly. Here are my current camera and mic settings.
_cam.setMode(160, 120, 30, false);
_cam.setQuality(0, 88);
_cam.setKeyFrameInterval(30);
_mic.rate = 11;
I'm also flushing the buffer before closing out the publish stream
_netstream.publish('null');
Could there be something going on with camera initialization/deactivation that causes the lag?
Any help would be greatly appreciated. Let me know if you need more details
I believe this has something to do with the way that the Flash plugin itself initializes and displays the camera.
If you set up a simple test to try setting and unsetting the video stream:
var cam:Camera = Camera.getCamera();
var webcam:Video = new Video(500, 375);
addChild(webcam);
var isPaused:Boolean = false;
function showWebcam():void {
if (!isPaused) {
cam = null;
} else {
cam = Camera.getCamera();
}
webcam.attachCamera(cam);
isPaused = !isPaused;
}
pausingButton.addEventListener(MouseEvent.CLICK, showWebcam);
You'll notice a definite pause as it switches between the two states.
From what I've seen, every time I call attachCamera() with a video object, there is a noticeable pause of the Flash Player itself (including all tweens, interactions, everything) when the method is called, even if the object I'm attaching is null.
Four seconds seems like an excessive lag, but I have noticed that the larger the input/video render and with smoothing = true set on the video object can affect the length of the delay.
As for a solution; I'm not sure if there is one achievable via pure Actionscript, since the delay appears to be down to how the Flash Player itself initializes and renders the live video object.

Which Is More Efficient, Embeddeing an FLV or Compiling an FLV to a SWF and Loading it Dynamically?

Recently I took on a rather daunting task of creating an interactive music video for the popular show Ghost Whisperer. I was brought in late in the project and was confronted with a giant authoring file (some 5000+ frames long). The previous programmer (who did everything in AS2) had essentially embedded a two and half minute FLV on the time main time line and at certain frame points would add in clips for interactivity with basic mouse click functionality that would trigger those clips to play other clips with some flash elements and some video elements.
(the final version I created can be viewed here http://www.gwghostmagic.com);
Being a neat freak I decided to rebuild the whole thing in AS3, ditch the time line altogether and rather load in my elements at run time so that instead of 5000 frames I had one frame and to trigger the actions added an Event.ENTER_FRAME event listener to trigger the interactivity when the loaded .swf would reach certain frames. From a programmers stand point it made everything much clearer and easier to manipulate certain actions. The problem was, the thing ran like crap.
The 5000+ frame version loaded faster and ran smoother than my AS3 version. Which brings me to the question, when is it better to embed and run off the time line than run off of an event listener? Is flash better suited for time line functionality?
Initially I decided to build and run off of a streaming .flv but syncing up the actions to the .flv was impossible because the .flv was running at 23.975 fps whereas my flash movie was running at 24 fps. Alas I was forced to compile an .swf with the flv embedded. That .swf was then loaded into my main .swf that contained all the functions and extra clips that would play according to the loaded .swf's frame position.
One thing I've noticed is that the Event.ENTER_FRAME seems to slow the whole damn application down because at every frame it has to run down a list of if statements 200 lines of code long. Wherein if the whole thing was embedded I might only need to insert a keyframe where the interactive clip would be instantiated and then would know immediately what to do, rather than cycle through if else statements.
Did I screw up trying to make things nice and tidy? Is it better to let flash be flash? Should I seek out another career? Any input is greatly appreciated.
I think that having the event trigger every frame as opposed to only triggering actions occasionally accounts for the performance difference. Hopefully you could minimize the amount of code and conditionals that was run through in the event handler.
Here's an idea I haven't tested:
var dispatch:Object = {
f1: function () {textBubble.text = "This is Rush Hour 2";},
f61: function () {textBubble.text = "";},
f111: function () {textBubble.text = "This scene has 50 takes; "
+"Jackie Chan said \"Square Madison\" each time.";},
f171: function () {textBubble.text = "";}
};
addEventListener(Event.ENTER_FRAME, function (e:event) {
if (dispatch["f"+e.target.currentFrame] is Function) {
dispatch["f"+e.target.currentFrame]();
}
});
This uses the Object as an Associative Array as described in the live docs.
You are probably correct - running 200+ lines of code on every frame is slowing you down. Instead of a huge list of statements, split your frame functions up and only call the ones you need - this is an expansion on dlamblin's idea - this way you are running the smallest amount of code necessary for each frame:
function doThisOnFrame20():void
{
//do stuff...
}
function doThisOnFrame50():void
{
//do stuff...
}
var frameFunctions:Array = new Array();
frameFunctions[20] = doThisOnFrame20;
frameFunctions[50] = doThisOnFrame50;
function enterFrameListener(event:Event):void
{
if(frameFunctions[swfWithFLV.currentFrame] is Function)
{
frameFunctions[swfWithFLV.currentFrame]();
}
}
addEventListener(Event.ENTER_FRAME,enterFrameListener);