floatinghotpot / cordova-plugin-nativeaudio

The low latency audio plugin is designed to enable low latency and polyphonic audio from Cordova/PhoneGap applications, using a very simple and basic API.
MIT License
235 stars 288 forks source link

Unloading occasionally results in an EXC_BAD_ACCESS on iOS #76

Open fleather opened 8 years ago

fleather commented 8 years ago

I tracked down the root cause of the problem:

The unload method in NativeAudio.m unloads the asset and removes the audio id from the mapping in this line:

[audioMapping removeObjectForKey: audioID];

Then the NativeAudioAsset object gets garbage collected. But in some cases the callback from the AVAudioPlayer (audioPlayerDidFinishPlaying) is called after the asset object which contains the AVAudioPlayer is already garbage collected, which results in the EXC_BAD_ACCES...

A possible fix could be that NOT the NativeAudioAsset object receives the callbacks from AVAudioPlayer, but the plugin itself (NativeAudio.m). Another possibility is to remove the NativeAudioAsset object from the mapping not before the "audioPlayerDidFinishPlaying" is called...

fleather commented 8 years ago

Unfortunately I don't have a decent understanding of Objective-C, so the only thing to get this currently working is commenting out the above mentioned line of code which causes the error.

I wonder that I seem to be the only one with this problem. I tested it on different phones...

Cloov commented 6 years ago

I think I'm also encountering this issue.

* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0xf8c47beb8) frame #0: 0x0000000185214430 libobjc.A.dylibobjc_msgSend + 16 frame #1: 0x000000018b775dd8 AVFAudio-[AVAudioPlayer(AVAudioPlayerPriv) finishedPlaying:] + 92 frame #2: 0x00000001869b32e4 Foundation__NSThreadPerformPerform + 340

As touched upon above, I believe it's to do with an attempt by the audio player to use its delegate reference, after it's been cleaned up due to the unload method. I'm not completely sure though.

In the unload method, could we add a call to set the player's delegate to nil?

    [self stop];
    for (int x = 0; x < [voices count]; x++) {
        AVAudioPlayer * player = [voices objectAtIndex:x];
        [player setDelegate:nil];
        player = nil;
    }
    voices = nil;

I think the incidence of this issue is at the mercy of how and when cleanup of the audio players takes place. If the sounds being played are short enough, and garbage collection is happening significantly later, then you'll get no calls to finishedPlaying after cleanup. If you had long sounds still playing, and aggressive garbage collection is taking place, then maybe this would be more frequent.