前段時(shí)間,做小示例的時(shí)候偶然發(fā)現(xiàn)了個(gè)問題(也許是我沒仔細(xì)了解過SimpleAudioEngine):
- 使用SimpleAudioEngine來播放背景音樂A
- 然后音樂開始播放了脯倒,使用preloadBackgroundMusic加載音樂B
- 然后使用isBackgroundMusicPlaying來查詢時(shí)杨幼,返回值為false
- 然后使用stopBackgroundMusic來關(guān)閉背景音樂時(shí),發(fā)現(xiàn)根本關(guān)不掉啊
從操作可以看出來,出問題的關(guān)鍵就在于,播放背景音樂時(shí),進(jìn)行了另外一個(gè)背景音樂的預(yù)加載采章,然而SimpleAudioEngine中,背景不能同時(shí)存在多個(gè)壶辜。
既然出現(xiàn)了問題悯舟,那就來看看源碼是怎么寫的(cocos2d-x 3.10)。
背景音樂預(yù)加載的代碼:
//audio/ios/SimpleAudioEngine.mm
void SimpleAudioEngine::preloadBackgroundMusic(const char* pszFilePath)
{
// Changing file path to full path
std::string fullPath = FileUtils::getInstance()->fullPathForFilename(pszFilePath);
static_preloadBackgroundMusic(fullPath.c_str());
}
這個(gè)函數(shù)中砸民,重點(diǎn)是第二句抵怎,調(diào)用了一個(gè)靜態(tài)方法static_preloadBackgroundMusic來加載背景音樂文件。這個(gè)方法的源碼:
//audio/ios/SimpleAudioEngine.mm
static void static_preloadBackgroundMusic(const char* pszFilePath)
{
[[SimpleAudioEngine sharedEngine] preloadBackgroundMusic: [NSString stringWithUTF8String: pszFilePath]];
}
看來是調(diào)用了ObjC封裝的接口岭参,繼續(xù)往下看:
//audio/ios/SimpleAudioEngine_objc.m
-(void) preloadBackgroundMusic:(NSString*) filePath {
[am preloadBackgroundMusic:filePath];
}
這里看到反惕,在Objc的接口中調(diào)用到了一個(gè)對象am的preloadBackgroundMusic方法。這個(gè)am演侯,我們在這個(gè).m文件的頭部可以看到姿染,它是一個(gè)CDAudioManager對象。所以這個(gè)對象里面的預(yù)加載是如何實(shí)現(xiàn)的呢:
//audio/ios/CDAudioManager.m
//Load background music ready for playing
-(void) preloadBackgroundMusic:(NSString*) filePath
{
[self.backgroundMusic load:filePath];
}
我們在這個(gè)文件中能夠找到很多backgroundMusic秒际,不能確定這里用到的是哪一個(gè)backgroundMusic悬赏。所以往上找,能看到它是屬于
CDAudioManager娄徊,然后在頭文件中可以看到:
@interface CDAudioManager : NSObject <CDLongAudioSourceDelegate, CDAudioInterruptProtocol, AVAudioSessionDelegate> {
CDSoundEngine *soundEngine;
CDLongAudioSource *backgroundMusic;
NSMutableArray *audioSourceChannels;
//....后面省略
其實(shí)闽颇,在這里我們就能看到問題了,這個(gè)CDAudioManager所持有的CDLongAudioSource引用只有一個(gè)嵌莉,也就是說进萄,表示我們的BackgroundMusic的資源對象只有一個(gè)。繼續(xù)看這個(gè)CDLongAudioSource的load函數(shù)是怎么實(shí)現(xiàn)的:
-(void) load:(NSString*) filePath {
//We have already loaded a file previously, check if we are being asked to load the same file
if (state == kLAS_Init || ![filePath isEqualToString:audioSourceFilePath]) {
CDLOGINFO(@"Denshion::CDLongAudioSource - Loading new audio source %@",filePath);
//New file
if (state != kLAS_Init) {
[audioSourceFilePath release];//Release old file path
[audioSourcePlayer release];//Release old AVAudioPlayer, they can't be reused
}
audioSourceFilePath = [filePath copy];
NSError *error = nil;
NSString *path = [CDUtilities fullPathFromRelativePath:audioSourceFilePath];
audioSourcePlayer = [(AVAudioPlayer*)[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
if (error == nil) {
[audioSourcePlayer prepareToPlay];
audioSourcePlayer.delegate = self;
if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceFileDidChange:)]) {
//Tell our delegate the file has changed
[delegate cdAudioSourceFileDidChange:self];
}
} else {
CDLOG(@"Denshion::CDLongAudioSource - Error initialising audio player: %@",error);
}
} else {
//Same file - just return it to a consistent state
[self pause];
[self rewind];
}
audioSourcePlayer.volume = volume;
audioSourcePlayer.numberOfLoops = numberOfLoops;
state = kLAS_Loaded;
}
在load函數(shù)中锐峭,如果新的Path與本對象中的屬性audioSourceFilePath不同中鼠,而且之前已經(jīng)加載過一個(gè)背景音樂,state不再是kLAS_Init沿癞,此時(shí)會釋放并覆蓋舊的audioSourcePlayer和audioSourceFilePath援雇。
即使你先加載了音樂A,再加載音樂B椎扬,然后再重新加載一次音樂A惫搏,雖然Path與之前的A相同,但是控制的并不再是同一個(gè)對象蚕涤,因?yàn)椤芭f”的引用總是會被丟棄筐赔。
到這里并沒有結(jié)束,我們還需要確定一下揖铜,播放背景音樂的時(shí)候和這個(gè)audioSource有關(guān)茴丰。看看playBackgroundMusic的代碼:
//audio/ios/SimpleAudioEngine.mm
void SimpleAudioEngine::playBackgroundMusic(const char* pszFilePath, bool bLoop)
{
// Changing file path to full path
std::string fullPath = FileUtils::getInstance()->fullPathForFilename(pszFilePath);
static_playBackgroundMusic(fullPath.c_str(), bLoop);
}
同樣,去找static_playBackgroundMusic方法:
static void static_playBackgroundMusic(const char* pszFilePath, bool bLoop)
{
[[SimpleAudioEngine sharedEngine] playBackgroundMusic: [NSString stringWithUTF8String: pszFilePath] loop: bLoop];
}
然后找到Obj-c的接口:
-(void) playBackgroundMusic:(NSString*) filePath
{
[am playBackgroundMusic:filePath loop:TRUE];
}
-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop
{
[am playBackgroundMusic:filePath loop:loop];
}
這里可以看到進(jìn)行了一個(gè)重載贿肩,playBackgroundMusic方法的默認(rèn)loop屬性為true峦椰。然后繼續(xù)看am中的代碼:
-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop
{
[self.backgroundMusic load:filePath];
if (loop) {
[self.backgroundMusic setNumberOfLoops:-1];
} else {
[self.backgroundMusic setNumberOfLoops:0];
}
if (!willPlayBackgroundMusic || _mute) {
CDLOGINFO(@"Denshion::CDAudioManager - play bgm aborted because audio is not exclusive or sound is muted");
return;
}
[self.backgroundMusic play];
}
這里第一句就是調(diào)用CDLongAudioSource的load方法,也就是之前我們看到的preloadBakcgroundMusic函數(shù)中所做的操作汰规。中間一段是進(jìn)行l(wèi)oop設(shè)置汤功,以及錯(cuò)誤處理。最后調(diào)用了self.backgroundMusic(CDLongAudioSource)的play方法溜哮。
結(jié)論:
至始至終滔金,SimpleAudioEngine中只持有了一個(gè)關(guān)于背景音樂的引用。所以茂嗓,進(jìn)行連續(xù)的preloadBackgroundMusic或者playBackgroundMusic鹦蠕,我們看到的現(xiàn)象都是丟失了對原來的背景音樂的控制(引用)。
使用SimpleAudioEngine播放背景音樂時(shí)在抛,如果游戲中有多個(gè)場景,每個(gè)場景有不同的背景音樂萧恕,我們沒有必要去一次preload所有的BackgroundMusic文件刚梭,注意每次播放背景音樂前進(jìn)行判斷,確保舊的背景音樂被停止票唆。
關(guān)于SimpleAudioEngine的測試代碼朴读,從我的Github中可以看到。如有錯(cuò)誤與不完整指出走趋,還請小伙伴指出衅金!