ttplayer绿色版 ttsplayer( 三 )


//需要录音时,AudioSession的设置代码如下:if ([AVAudioSession sharedInstance].category != AVAudioSessionCategoryPlayAndRecord) {[RTCAudioSessionCacheManager cacheCurrentAudioSession];AVAudioSessionCategoryOptions categoryOptions = AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionMixWithOthers;if (@available(iOS 10.0, *)) {categoryOptions |= AVAudioSessionCategoryOptionAllowBluetooth;}[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:categoryOptions error:nil];[[AVAudioSession sharedInstance] setActive:YES error:nil];}//功能结束时重置audioSession[RTCAudioSessionCacheManager resetToCachedAudioSession];static AVAudioSessionCategory cachedCategory = nil;static AVAudioSessionCategoryOptions cachedCategoryOptions = nil;@implementation RTCAudioSessionCacheManager//更改audioSession前缓存RTC当下的设置+ (void)cacheCurrentAudioSession {if (![[AVAudioSession sharedInstance].category isEqualToString:AVAudioSessionCategoryPlayback] && ![[AVAudioSession sharedInstance].category isEqualToString:AVAudioSessionCategoryPlayAndRecord]) {return;}@synchronized (self) {cachedCategory = [AVAudioSession sharedInstance].category;cachedCategoryOptions = [AVAudioSession sharedInstance].categoryOptions;}}//重置到缓存的audioSession设置+ (void)resetToCachedAudioSession {if (!cachedCategory || !cachedCategoryOptions) {return;}BOOL needResetAudioSession = ![[AVAudioSession sharedInstance].category isEqualToString:cachedCategory] || [AVAudioSession sharedInstance].categoryOptions != cachedCategoryOptions;if (needResetAudioSession) {dispatch_async(dispatch_get_global_queue(0, 0), ^{[[AVAudioSession sharedInstance] setCategory:cachedCategory withOptions:cachedCategoryOptions error:nil];[[AVAudioSession sharedInstance] setActive:YES error:nil];@synchronized (self) {cachedCategory = nil;cachedCategoryOptions = nil;}});}}@end兜底策略考虑到在线教室场景的复杂度,让教室内所有功能代码都遵循 AVAudioSession 的修改规范 , 虽然有严格的 codeReview,但是也存在一定的人为因素风险,随着业务功能不断迭代 , 无法完全保证线上不出问题,因此一套可靠的兜底策略显得非常有必要 。
兜底策略的基本逻辑是 hook 到 AVAudioSession 的变化,当各模块对 AVAudioSession 的设置不符合规范要求时,我们在不影响功能的前提下强制进行修正,比如对 options 补充上混音模式 。
通过方法交换我们可以 hook 到 AVAudioSession 的更改 。比如用 kk_setCategory:withOptions: error: 与系统的 setCategory:withOptions: error: 进行交换,在交换的方法里,我们判断 options 是否包含 AVAudioSessionCategoryOptionMixWithOthers,如果没有包含我们就进行追加 。
- (BOOL)kk_setCategory:(AVAudioSessionCategory)category withOptions:(AVAudioSessionCategoryOptions)options error:(NSError **)outError {//在需要进行对audioSession进行修正的场景下(RTC直播),修改options时未包含mixWithOther , 则给options追加mixWithOtherBOOL addMixWithOthersEnable = shouldFixAudioSession && !(options & AVAudioSessionCategoryOptionMixWithOthers)];if (addMixWithOthersEnable) {return [self kk_setCategory:category withOptions:options | AVAudioSessionCategoryOptionMixWithOthers error:outError];;}return [self kk_setCategory:category withOptions:options error:outError];}但上述方法只对通过调用 setCategory:withOptions: error: 来设置 AVAudioSession 有效,如果某个模块调用 setCategory:error: 方法来设置 AVAudioSession,setCategory:error: 方法默认会将options设置为 0(未包含AVAudioSessionCategoryOptionMixWithOthers) 。我们 hook 到 setCategory:error: 方法后 , 无法通过调整参数的方式来为options追加混音模式选项,但是可以在交换的方法内改为调用 setCategory:withOptions:error: 方法,并将 options 参数传入AVAudioSessionCategoryOptionMixWithOthers , 来满足我们的需求 。可问题在于调用 setCategory:withOptions:error: 时,底层会再嵌套调用 setCategory:error: 方法,而此时setCategory:error: 已经被我们hook并且在交换的方法内调用了setCategory:withOptions:error: , 如此便形成了死循环 。

推荐阅读