Playing Audio with actions: CallFunc lambda never called

Since cocos2dx’s AudioEngine doesn’t really provide any methods for delaying audio play, I’ve decided to try using actions for this purpose.

I was aiming to create a list of delayed, overlapping ambient tracks.
Used cocos2d::DelayTime actions interleaved with calls to my Play function like this:

cocos2d::Vector<cocos2d::FiniteTimeAction*> actionList;
for(unsigned int i = 0; i < _ambientBlock.size(); ++i)
{
    if(_ambientBlock[i].timeOffset == 0.0f)
    {
        // do not push any delay before this track, starts playing immediately
    }
    else if(i == 0) // first element, don't need to calculate, just read value
    {
        actionList.pushBack(cocos2d::DelayTime::create((float)(_ambientBlock[i].timeOffset)));
    }
    else // calculate proper delay time, take into account previously accumulated delay
    {
        float precedingDelay = ((float)_ambientBlock[i].timeOffset) - ((float)_ambientBlock[i - 1].timeOffset);
        actionList.pushBack(cocos2d::DelayTime::create(precedingDelay));
    }
    actionList.pushBack(cocos2d::CallFunc::create([i](){
        PlayAmbient(_ambientBlock[i].fileName);
    }));
}
cocos2d::Sequence* sequence = cocos2d::Sequence::create(actionList);
_audioEntity->runAction(sequence);

However it looks like the lambda is never called. If I put a breakpoint on

PlayAmbient(_ambientBlock[i].fileName);

It is never hit. Nothing’s being played either.

_audioEntity is just a cocos2d::Node that’s been created once at the beginning of the program and retained with CC_SAFE_RETAIN. It’s not a part of any scene.

I have no clue how to even start solving this.

I’m using cocos2dx 3.17.

3.17?

Tell us more about PlayAmbient and how it is defined. Is it subclassing anything?

So you are storing all the actions you want in a vector and then you are going to pass that vector to a sequence to have the actions executed?

Yes, I meant 3.17 of course, made a typo. Will edit the post now.

Play ambient is declared as follows:

static void PlayAmbient(const std::string& resourcePath);

It’s a member of an AudioSystem class with everything static since I only ever need one system like that running.

It uses AudioEngine::play2d internally.

So you are storing all the actions you want in a vector and then you are going to pass that vector to a sequence to have the actions executed?

Yes, exactly. I actually included the part where I create the sequence after the loop is done.

I’ve tried to replace:

actionList.pushBack(cocos2d::CallFunc::create([i](){
    PlayAmbient(_ambientBlock[i].fileName);
}));

with:

const std::function<void()> callback = std::bind(AudioSystem::TestFunction);
actionList.pushBack(cocos2d::CallFunc::create(callback));

where TestFunction is just:

static void TestFunction();

This isn’t being called either, so I know the problem is not in the lambda itself, but more likely in runAction not being run properly.

I will see if I can test this.

Essentially a debugging problem.

Lambda functions are notorious for not behaving well with breakpoints. Throw in a CCLOG(); cclog(); and/or printf() to see if it is called.

Try first manual sequence without array.
Put a .5f second delay time between each play.
Sequence(CallFunc, Delay, CallFunc, Delay, CallFunc ...etc .. , null);
Then put them into Vector with pushBack as you have it.

Only after that’s working try your code with some printf/cclog to check code path. Maybe _ambientBlock has size 0? Maybe _audioEntity isn’t actually running, and thus sequence is never started.

good luck.

1 Like

Nope. Not called. Nothing’s printed out.

Not called either.

Not the case. It has proper, expected data. Double checked that stepping into code with the debugger.

Now this starts to look like something… I’ve added following code:

bool running = _audioEntity->isRunning();
_audioEntity->runAction(sequence);
running = _audioEntity->isRunning();

Stepped through and it’s false in both cases.

Now what does it mean for a Node to be “running”? From your answer I can figure that this state is required to be able to run any actions on the Node.

How can I make sure it is running? Does it have to be a part of a scene that’s currently running? If not, can I somehow manually change it’s state to running?

Can’t seem to find any documentation covering the subject and the only method that directly names “running” (and does not refer to actions) is the one I just called.

_audioEntity needs to be added as a child of some node that’s ‘running’, which essentially just means it needs to exist in the current Scene’s node hierarchy graph.

You can look into ActionManager and manually schedule, but easiest is to add it as a child like any other visible Sprite/Node.

Thanks, that clears up a lot, but there’s one thing I’m in doubt about. Since I intend to use this node for running audio-related sequences, it is possible that the current scene will at some point no longer be current during the action running time.

I suspect this might interfere.

Is that the case? If yes, is there any way to have the Node running independent of scenes?