// // AudioSessionManager.m // keyBoard // // Created by Mac on 2026/1/15. // #import "AudioSessionManager.h" @interface AudioSessionManager () @property(nonatomic, assign) BOOL isSessionActive; @end @implementation AudioSessionManager #pragma mark - Singleton + (instancetype)sharedManager { static AudioSessionManager *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[AudioSessionManager alloc] init]; }); return instance; } - (instancetype)init { self = [super init]; if (self) { _isSessionActive = NO; [self setupNotifications]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark - Notifications - (void)setupNotifications { // 监听音频会话中断通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleInterruption:) name:AVAudioSessionInterruptionNotification object:nil]; // 监听音频路由变化通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleRouteChange:) name:AVAudioSessionRouteChangeNotification object:nil]; } - (void)handleInterruption:(NSNotification *)notification { NSDictionary *info = notification.userInfo; AVAudioSessionInterruptionType type = [info[AVAudioSessionInterruptionTypeKey] unsignedIntegerValue]; if (type == AVAudioSessionInterruptionTypeBegan) { // 中断开始:来电、闹钟等 dispatch_async(dispatch_get_main_queue(), ^{ if ([self.delegate respondsToSelector:@selector(audioSessionManagerDidInterrupt:)]) { [self.delegate audioSessionManagerDidInterrupt: KBAudioSessionInterruptionTypeBegan]; } }); } else if (type == AVAudioSessionInterruptionTypeEnded) { // 中断结束 AVAudioSessionInterruptionOptions options = [info[AVAudioSessionInterruptionOptionKey] unsignedIntegerValue]; if (options & AVAudioSessionInterruptionOptionShouldResume) { // 可以恢复播放 [self activateSession:nil]; } dispatch_async(dispatch_get_main_queue(), ^{ if ([self.delegate respondsToSelector:@selector(audioSessionManagerDidInterrupt:)]) { [self.delegate audioSessionManagerDidInterrupt: KBAudioSessionInterruptionTypeEnded]; } }); } } - (void)handleRouteChange:(NSNotification *)notification { NSDictionary *info = notification.userInfo; AVAudioSessionRouteChangeReason reason = [info[AVAudioSessionRouteChangeReasonKey] unsignedIntegerValue]; switch (reason) { case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: case AVAudioSessionRouteChangeReasonNewDeviceAvailable: { // 旧设备不可用(如耳机拔出)或新设备可用(如耳机插入) dispatch_async(dispatch_get_main_queue(), ^{ if ([self.delegate respondsToSelector:@selector (audioSessionManagerRouteDidChange)]) { [self.delegate audioSessionManagerRouteDidChange]; } }); break; } default: break; } } #pragma mark - Microphone Permission - (void)requestMicrophonePermission:(void (^)(BOOL))completion { AVAudioSession *session = [AVAudioSession sharedInstance]; [session requestRecordPermission:^(BOOL granted) { dispatch_async(dispatch_get_main_queue(), ^{ if (!granted) { if ([self.delegate respondsToSelector:@selector (audioSessionManagerMicrophonePermissionDenied)]) { [self.delegate audioSessionManagerMicrophonePermissionDenied]; } } if (completion) { completion(granted); } }); }]; } - (BOOL)hasMicrophonePermission { AVAudioSession *session = [AVAudioSession sharedInstance]; return session.recordPermission == AVAudioSessionRecordPermissionGranted; } #pragma mark - Session Configuration - (BOOL)configureForConversation:(NSError **)error { AVAudioSession *session = [AVAudioSession sharedInstance]; // 配置为录音+播放模式 // Category: PlayAndRecord - 同时支持录音和播放 // Mode: VoiceChat - 允许系统进行 AGC/降噪,提升输入音量 // Options: // - DefaultToSpeaker: 默认使用扬声器 // - AllowBluetooth: 允许蓝牙设备 NSError *categoryError = nil; BOOL success = [session setCategory:AVAudioSessionCategoryPlayAndRecord mode:AVAudioSessionModeVoiceChat options:(AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionAllowBluetooth) error:&categoryError]; if (!success) { if (error) { *error = categoryError; } NSLog(@"[AudioSessionManager] Failed to configure session: %@", categoryError.localizedDescription); return NO; } // 优先使用后端要求的采样率和缓冲时长,减少重采样误差 NSError *sampleRateError = nil; [session setPreferredSampleRate:16000 error:&sampleRateError]; if (sampleRateError) { NSLog(@"[AudioSessionManager] Failed to set sample rate: %@", sampleRateError.localizedDescription); } NSError *bufferError = nil; [session setPreferredIOBufferDuration:0.02 error:&bufferError]; if (bufferError) { NSLog(@"[AudioSessionManager] Failed to set IO buffer: %@", bufferError.localizedDescription); } if ([session respondsToSelector:@selector(setPreferredInputNumberOfChannels: error:)]) { NSError *channelsError = nil; [session setPreferredInputNumberOfChannels:1 error:&channelsError]; if (channelsError) { NSLog(@"[AudioSessionManager] Failed to set input channels: %@", channelsError.localizedDescription); } } return YES; } - (BOOL)configureForPlayback:(NSError **)error { AVAudioSession *session = [AVAudioSession sharedInstance]; // 仅播放模式 NSError *categoryError = nil; BOOL success = [session setCategory:AVAudioSessionCategoryPlayback mode:AVAudioSessionModeDefault options:AVAudioSessionCategoryOptionDefaultToSpeaker error:&categoryError]; if (!success) { if (error) { *error = categoryError; } NSLog(@"[AudioSessionManager] Failed to configure playback: %@", categoryError.localizedDescription); return NO; } return YES; } - (BOOL)activateSession:(NSError **)error { if (self.isSessionActive) { return YES; } AVAudioSession *session = [AVAudioSession sharedInstance]; NSError *activationError = nil; BOOL success = [session setActive:YES error:&activationError]; if (!success) { if (error) { *error = activationError; } NSLog(@"[AudioSessionManager] Failed to activate session: %@", activationError.localizedDescription); return NO; } if (session.isInputGainSettable) { NSError *gainError = nil; [session setInputGain:1.0 error:&gainError]; if (gainError) { NSLog(@"[AudioSessionManager] Failed to set input gain: %@", gainError.localizedDescription); } } self.isSessionActive = YES; return YES; } - (void)deactivateSession { if (!self.isSessionActive) { return; } AVAudioSession *session = [AVAudioSession sharedInstance]; NSError *error = nil; // 使用 NotifyOthersOnDeactivation 通知其他应用可以恢复播放 [session setActive:NO withOptions:AVAudioSessionSetActiveOptionNotifyOthersOnDeactivation error:&error]; if (error) { NSLog(@"[AudioSessionManager] Failed to deactivate session: %@", error.localizedDescription); } self.isSessionActive = NO; } @end