Untitled
import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; import notifee from '@notifee/react-native'; export type IOSInterruptionType = | 'phone_call' | 'facetime' | 'siri' | 'system_alert' | 'other_app' | 'system_pressure' | 'media_playback' | 'unknown'; export type IOSInterruptionEvent = { type: 'began' | 'ended'; reason: IOSInterruptionType; options?: { shouldResume?: boolean; wasSuspended?: boolean; }; }; class IOSAudioSessionManager { private listeners: Set<(event: IOSInterruptionEvent) => void> = new Set(); private audioSessionEmitter?: NativeEventEmitter; private memoryWarningEmitter?: NativeEventEmitter; constructor() { if (Platform.OS === 'ios') { this.setupAudioSession(); this.setupInterruptionListeners(); this.setupMemoryWarningListener(); } } private async setupAudioSession() { try { const { RNAudioRecorderPlayer } = NativeModules; await RNAudioRecorderPlayer.setAudioSession({ category: 'playAndRecord', options: [ 'allowBluetooth', 'allowBluetoothA2DP', 'mixWithOthers', 'defaultToSpeaker' ], mode: 'spokenAudio', categoryOptions: { mixWithOthers: false, duckOthers: true, interruptSpokenAudioAndMixWithOthers: false, allowBluetooth: true, allowBluetoothA2DP: true, allowAirPlay: true, defaultToSpeaker: true, } }); } catch (error) { console.error('Failed to configure audio session:', error); } } private setupInterruptionListeners() { const { RNAudioRecorderPlayer } = NativeModules; this.audioSessionEmitter = new NativeEventEmitter(RNAudioRecorderPlayer); // Audio Session Interruptions this.audioSessionEmitter.addListener( 'audioSessionInterruption', this.handleAudioInterruption ); // Route changes (e.g., audio output changes) this.audioSessionEmitter.addListener( 'audioRouteChange', this.handleAudioRouteChange ); // Media Services were reset this.audioSessionEmitter.addListener( 'mediaServicesWereLost', this.handleMediaServicesReset ); this.audioSessionEmitter.addListener( 'mediaServicesWereReset', this.handleMediaServicesReset ); } private setupMemoryWarningListener() { // System memory warnings this.memoryWarningEmitter = new NativeEventEmitter(NativeModules.RNEventEmitter); this.memoryWarningEmitter.addListener('memoryWarning', this.handleMemoryWarning); } private handleAudioInterruption = (event: any) => { const interruptionType = this.getInterruptionType(event); this.notifyListeners({ type: event.type === 'began' ? 'began' : 'ended', reason: interruptionType, options: { shouldResume: event.type === 'ended' && event.options?.shouldResume, wasSuspended: event.wasSuspended } }); this.updateNotification(event.type, interruptionType); }; private handleAudioRouteChange = (event: any) => { // Handle audio route changes (e.g., between speaker, bluetooth) this.notifyListeners({ type: 'began', reason: 'other_app', options: { shouldResume: true } }); }; private handleMediaServicesReset = () => { // Handle media service resets (rare but can happen) this.notifyListeners({ type: 'began', reason: 'system_pressure', options: { shouldResume: true } }); }; private handleMemoryWarning = () => { this.notifyListeners({ type: 'began', reason: 'system_pressure', options: { shouldResume: false } }); }; private getInterruptionType(event: any): IOSInterruptionType { // iOS provides this in AVAudioSession interruption notifications switch (event.reason) { case 'AVAudioSessionInterruptionTypePhoneCall': return 'phone_call'; case 'AVAudioSessionInterruptionTypeFaceTime': return 'facetime'; case 'AVAudioSessionInterruptionTypeSiri': return 'siri'; case 'AVAudioSessionInterruptionTypeSystemAlert': return 'system_alert'; case 'AVAudioSessionInterruptionTypeMediaPlayback': return 'media_playback'; default: return 'unknown'; } } private async updateNotification( state: 'began' | 'ended', type: IOSInterruptionType ) { const messages = { phone_call: 'Phone Call', facetime: 'FaceTime Call', siri: 'Siri', system_alert: 'System Alert', other_app: 'Other App Audio', system_pressure: 'System Resources', media_playback: 'Media Playback', unknown: 'System Interruption' }; await notifee.displayNotification({ id: 'recording-notification', title: state === 'began' ? 'Recording Paused' : 'Recording Resumed', body: state === 'began' ? `Recording paused due to ${messages[type]}` : 'Recording has resumed', ios: { categoryId: 'recording', interruptionLevel: 'active' } }); } public addListener(listener: (event: IOSInterruptionEvent) => void): () => void { this.listeners.add(listener); return () => this.listeners.delete(listener); } private notifyListeners(event: IOSInterruptionEvent): void { this.listeners.forEach(listener => listener(event)); } public cleanup(): void { if (this.audioSessionEmitter) { this.audioSessionEmitter.removeAllListeners('audioSessionInterruption'); this.audioSessionEmitter.removeAllListeners('audioRouteChange'); this.audioSessionEmitter.removeAllListeners('mediaServicesWereLost'); this.audioSessionEmitter.removeAllListeners('mediaServicesWereReset'); } if (this.memoryWarningEmitter) { this.memoryWarningEmitter.removeAllListeners('memoryWarning'); } this.listeners.clear(); } } export const iOSAudioManager = new IOSAudioSessionManager();
Leave a Comment