11import { Asset } from 'expo-asset' ;
2- import { Audio , InterruptionModeIOS } from 'expo-av' ;
2+ import { Audio , type AVPlaybackSource , InterruptionModeIOS } from 'expo-av' ;
3+ import { Platform } from 'react-native' ;
4+ import Sound from 'react-native-sound' ;
35
46import { logger } from '@/lib/logging' ;
57
8+ // Enable playback in silence mode
9+ Sound . setCategory ( 'Ambient' , true ) ;
10+
611class AudioService {
712 private static instance : AudioService ;
8-
9- // Use expo-av Sound objects
10- private startTransmittingSound : Audio . Sound | null = null ;
11- private stopTransmittingSound : Audio . Sound | null = null ;
12- private connectedDeviceSound : Audio . Sound | null = null ;
13- private connectToAudioRoomSound : Audio . Sound | null = null ;
14- private disconnectedFromAudioRoomSound : Audio . Sound | null = null ;
15-
13+ // Use specific type for react-native-sound instances
14+ private startTransmittingSound : Sound | null = null ;
15+ private stopTransmittingSound : Sound | null = null ;
16+
17+ // Keep others as expo-av for now
18+ private connectedDeviceSound : Sound | null = null ;
19+ private connectToAudioRoomSound : Sound | null = null ;
20+ private disconnectedFromAudioRoomSound : Sound | null = null ;
1621 private isInitialized = false ;
1722
1823 private constructor ( ) {
@@ -37,12 +42,15 @@ class AudioService {
3742
3843 try {
3944 // Configure audio mode for production builds
45+ // Note: react-native-sound handles its own session category (Ambient),
46+ // ensuring expo-av doesn't conflict is still good practice if used elsewhere.
4047 await Audio . setAudioModeAsync ( {
4148 allowsRecordingIOS : true ,
4249 staysActiveInBackground : true ,
4350 playsInSilentModeIOS : true ,
4451 shouldDuckAndroid : true ,
45- playThroughEarpieceAndroid : false ,
52+ // For speaker, we want false (speaker). For others, simple routing.
53+ playThroughEarpieceAndroid : false ,
4654 interruptionModeIOS : InterruptionModeIOS . DoNotMix ,
4755 } ) ;
4856
@@ -88,25 +96,60 @@ class AudioService {
8896
8997 private async loadAudioFiles ( ) : Promise < void > {
9098 try {
91- // Load start transmitting sound
92- const { sound : startSound } = await Audio . Sound . createAsync ( require ( '@assets/audio/ui/space_notification1.mp3' ) ) ;
93- this . startTransmittingSound = startSound ;
99+ // Load start transmitting sound (react-native-sound)
100+ const startTransmittingSoundAsset = Asset . fromModule ( require ( '@assets/audio/ui/space_notification1.mp3' ) ) ;
101+ await startTransmittingSoundAsset . downloadAsync ( ) ;
102+ const startUri = startTransmittingSoundAsset . localUri || startTransmittingSoundAsset . uri ;
103+
104+ this . startTransmittingSound = new Sound ( startUri , '' , ( error ) => {
105+ if ( error ) {
106+ logger . error ( { message : 'Failed to load start transmitting sound' , context : { error } } ) ;
107+ }
108+ } ) ;
94109
95- // Load stop transmitting sound
96- const { sound : stopSound } = await Audio . Sound . createAsync ( require ( '@assets/audio/ui/space_notification2.mp3' ) ) ;
97- this . stopTransmittingSound = stopSound ;
110+ // Load stop transmitting sound (react-native-sound)
111+ const stopTransmittingSoundAsset = Asset . fromModule ( require ( '@assets/audio/ui/space_notification2.mp3' ) ) ;
112+ await stopTransmittingSoundAsset . downloadAsync ( ) ;
113+ const stopUri = stopTransmittingSoundAsset . localUri || stopTransmittingSoundAsset . uri ;
98114
99- // Load connected device sound
100- const { sound : connectedSound } = await Audio . Sound . createAsync ( require ( '@assets/audio/ui/positive_interface_beep.mp3' ) ) ;
101- this . connectedDeviceSound = connectedSound ;
115+ this . stopTransmittingSound = new Sound ( stopUri , '' , ( error ) => {
116+ if ( error ) {
117+ logger . error ( { message : 'Failed to load stop transmitting sound' , context : { error } } ) ;
118+ }
119+ } ) ;
102120
103- // Load connect to audio room sound
104- const { sound : connectRoomSound } = await Audio . Sound . createAsync ( require ( '@assets/audio/ui/software_interface_start.mp3' ) ) ;
105- this . connectToAudioRoomSound = connectRoomSound ;
121+ // Load connected device sound (react-native-sound)
122+ const connectedDeviceSoundAsset = Asset . fromModule ( require ( '@assets/audio/ui/positive_interface_beep.mp3' ) ) ;
123+ await connectedDeviceSoundAsset . downloadAsync ( ) ;
124+ const connectedUri = connectedDeviceSoundAsset . localUri || connectedDeviceSoundAsset . uri ;
106125
107- // Load disconnect from audio room sound
108- const { sound : disconnectRoomSound } = await Audio . Sound . createAsync ( require ( '@assets/audio/ui/software_interface_back.mp3' ) ) ;
109- this . disconnectedFromAudioRoomSound = disconnectRoomSound ;
126+ this . connectedDeviceSound = new Sound ( connectedUri , '' , ( error ) => {
127+ if ( error ) {
128+ logger . error ( { message : 'Failed to load connected device sound' , context : { error } } ) ;
129+ }
130+ } ) ;
131+
132+ // Load connect to audio room sound (react-native-sound)
133+ const connectToAudioRoomSoundAsset = Asset . fromModule ( require ( '@assets/audio/ui/software_interface_start.mp3' ) ) ;
134+ await connectToAudioRoomSoundAsset . downloadAsync ( ) ;
135+ const connectRoomUri = connectToAudioRoomSoundAsset . localUri || connectToAudioRoomSoundAsset . uri ;
136+
137+ this . connectToAudioRoomSound = new Sound ( connectRoomUri , '' , ( error ) => {
138+ if ( error ) {
139+ logger . error ( { message : 'Failed to load connect to audio room sound' , context : { error } } ) ;
140+ }
141+ } ) ;
142+
143+ // Load disconnect from audio room sound (react-native-sound)
144+ const disconnectedFromAudioRoomSoundAsset = Asset . fromModule ( require ( '@assets/audio/ui/software_interface_back.mp3' ) ) ;
145+ await disconnectedFromAudioRoomSoundAsset . downloadAsync ( ) ;
146+ const disconnectRoomUri = disconnectedFromAudioRoomSoundAsset . localUri || disconnectedFromAudioRoomSoundAsset . uri ;
147+
148+ this . disconnectedFromAudioRoomSound = new Sound ( disconnectRoomUri , '' , ( error ) => {
149+ if ( error ) {
150+ logger . error ( { message : 'Failed to load disconnect from audio room sound' , context : { error } } ) ;
151+ }
152+ } ) ;
110153
111154 logger . debug ( {
112155 message : 'Audio files loaded successfully' ,
@@ -119,10 +162,21 @@ class AudioService {
119162 }
120163 }
121164
165+ // Remove old playSound helper as we are using Sound directly now
166+ // private async playSound...
167+
168+ // Updated to use react-native-sound
122169 async playStartTransmittingSound ( ) : Promise < void > {
123170 try {
124171 if ( this . startTransmittingSound ) {
125- await this . startTransmittingSound . replayAsync ( ) ;
172+ // Stop if playing
173+ this . startTransmittingSound . stop ( ) ;
174+ // Play
175+ this . startTransmittingSound . play ( ( success ) => {
176+ if ( ! success ) {
177+ logger . warn ( { message : 'Failed to play start transmitting sound (playback error)' } ) ;
178+ }
179+ } ) ;
126180 }
127181 } catch ( error ) {
128182 logger . error ( {
@@ -132,11 +186,19 @@ class AudioService {
132186 }
133187 }
134188
189+ // Updated to use react-native-sound
135190 async playStopTransmittingSound ( ) : Promise < void > {
136191 try {
137- if ( this . stopTransmittingSound ) {
138- await this . stopTransmittingSound . replayAsync ( ) ;
139- }
192+ if ( this . stopTransmittingSound ) {
193+ // Stop if playing
194+ this . stopTransmittingSound . stop ( ) ;
195+ // Play
196+ this . stopTransmittingSound . play ( ( success ) => {
197+ if ( ! success ) {
198+ logger . warn ( { message : 'Failed to play stop transmitting sound (playback error)' } ) ;
199+ }
200+ } ) ;
201+ }
140202 } catch ( error ) {
141203 logger . error ( {
142204 message : 'Failed to play stop transmitting sound' ,
@@ -148,8 +210,14 @@ class AudioService {
148210 async playConnectedDeviceSound ( ) : Promise < void > {
149211 try {
150212 if ( this . connectedDeviceSound ) {
151- await this . connectedDeviceSound . replayAsync ( ) ;
152- logger . debug ( { message : 'Sound played successfully' , context : { soundName : 'connectedDevice' } } ) ;
213+ this . connectedDeviceSound . stop ( ) ;
214+ this . connectedDeviceSound . play ( ( success ) => {
215+ if ( success ) {
216+ logger . debug ( { message : 'Sound played successfully' , context : { soundName : 'connectedDevice' } } ) ;
217+ } else {
218+ logger . warn ( { message : 'Failed to play connected device sound (playback error)' } ) ;
219+ }
220+ } ) ;
153221 }
154222 } catch ( error ) {
155223 logger . error ( {
@@ -161,9 +229,14 @@ class AudioService {
161229
162230 async playConnectToAudioRoomSound ( ) : Promise < void > {
163231 try {
164- if ( this . connectToAudioRoomSound ) {
165- await this . connectToAudioRoomSound . replayAsync ( ) ;
166- }
232+ if ( this . connectToAudioRoomSound ) {
233+ this . connectToAudioRoomSound . stop ( ) ;
234+ this . connectToAudioRoomSound . play ( ( success ) => {
235+ if ( ! success ) {
236+ logger . warn ( { message : 'Failed to play connect to audio room sound' } ) ;
237+ }
238+ } ) ;
239+ }
167240 } catch ( error ) {
168241 logger . error ( {
169242 message : 'Failed to play connected to audio room sound' ,
@@ -174,9 +247,14 @@ class AudioService {
174247
175248 async playDisconnectedFromAudioRoomSound ( ) : Promise < void > {
176249 try {
177- if ( this . disconnectedFromAudioRoomSound ) {
178- await this . disconnectedFromAudioRoomSound . replayAsync ( ) ;
179- }
250+ if ( this . disconnectedFromAudioRoomSound ) {
251+ this . disconnectedFromAudioRoomSound . stop ( ) ;
252+ this . disconnectedFromAudioRoomSound . play ( ( success ) => {
253+ if ( ! success ) {
254+ logger . warn ( { message : 'Failed to play disconnected from audio room sound' } ) ;
255+ }
256+ } ) ;
257+ }
180258 } catch ( error ) {
181259 logger . error ( {
182260 message : 'Failed to play disconnected from audio room sound' ,
@@ -187,21 +265,35 @@ class AudioService {
187265
188266 async cleanup ( ) : Promise < void > {
189267 try {
190- const sounds = [ this . startTransmittingSound , this . stopTransmittingSound , this . connectedDeviceSound , this . connectToAudioRoomSound , this . disconnectedFromAudioRoomSound ] ;
268+ // Unload start transmitting sound
269+ if ( this . startTransmittingSound ) {
270+ this . startTransmittingSound . release ( ) ;
271+ this . startTransmittingSound = null ;
272+ }
191273
192- await Promise . all (
193- sounds . map ( async ( sound ) => {
194- if ( sound ) {
195- await sound . unloadAsync ( ) ;
196- }
197- } )
198- ) ;
199-
200- this . startTransmittingSound = null ;
201- this . stopTransmittingSound = null ;
202- this . connectedDeviceSound = null ;
203- this . connectToAudioRoomSound = null ;
204- this . disconnectedFromAudioRoomSound = null ;
274+ // Unload stop transmitting sound
275+ if ( this . stopTransmittingSound ) {
276+ this . stopTransmittingSound . release ( ) ;
277+ this . stopTransmittingSound = null ;
278+ }
279+
280+ // Unload connected device sound
281+ if ( this . connectedDeviceSound ) {
282+ this . connectedDeviceSound . release ( ) ;
283+ this . connectedDeviceSound = null ;
284+ }
285+
286+ // Unload connect to audio room sound
287+ if ( this . connectToAudioRoomSound ) {
288+ this . connectToAudioRoomSound . release ( ) ;
289+ this . connectToAudioRoomSound = null ;
290+ }
291+
292+ // Unload disconnect from audio room sound
293+ if ( this . disconnectedFromAudioRoomSound ) {
294+ this . disconnectedFromAudioRoomSound . release ( ) ;
295+ this . disconnectedFromAudioRoomSound = null ;
296+ }
205297
206298 this . isInitialized = false ;
207299
0 commit comments