@@ -15,53 +15,61 @@ const AudioSharing = () => {
15
15
const [ connectionStatus , setConnectionStatus ] = useState <
16
16
'Not Connected' | 'Connecting' | 'Connected'
17
17
> ( 'Not Connected' ) ;
18
+
19
+ // Use refs for persistent state across remounts
18
20
const socketRef = useRef < Socket | null > ( null ) ;
19
21
const peerRef = useRef < Instance | null > ( null ) ;
20
22
const audioStreamRef = useRef < MediaStream | null > ( null ) ;
21
- const initializedRef = useRef ( false ) ;
22
23
const audioElementsRef = useRef < HTMLAudioElement [ ] > ( [ ] ) ;
24
+ const mountCountRef = useRef ( 0 ) ;
25
+ const lastCleanupTimeRef = useRef ( 0 ) ;
23
26
27
+ // Constants
28
+ const CLEANUP_THRESHOLD = 1000 ; // Minimum time between cleanups in ms
24
29
const SERVER_URL =
25
30
process . env . NEXT_PUBLIC_AUDIO_SERVER_URL || 'http://localhost:5555' ;
26
-
27
31
const TURN_SERVER = process . env . NEXT_PUBLIC_TURN_SERVER || '' ;
28
32
const TURN_USERNAME = process . env . NEXT_PUBLIC_TURN_USERNAME ;
29
33
const TURN_CREDENTIAL = process . env . NEXT_PUBLIC_TURN_PASSWORD ;
30
34
31
- const cleanupAudio = ( ) => {
35
+ const cleanupAudio = ( force = false ) => {
36
+ const now = Date . now ( ) ;
37
+
38
+ // Prevent rapid cleanup unless forced
39
+ if ( ! force && now - lastCleanupTimeRef . current < CLEANUP_THRESHOLD ) {
40
+ console . log ( 'Skipping cleanup due to threshold' ) ;
41
+ return ;
42
+ }
43
+
32
44
console . log ( 'Cleaning up audio connections...' ) ;
45
+ lastCleanupTimeRef . current = now ;
33
46
34
- // Stop and cleanup all audio tracks
35
47
if ( audioStreamRef . current ) {
36
48
audioStreamRef . current . getTracks ( ) . forEach ( ( track ) => {
37
49
track . stop ( ) ;
38
50
} ) ;
39
51
audioStreamRef . current = null ;
40
52
}
41
53
42
- // Destroy peer connection
43
54
if ( peerRef . current ) {
44
55
peerRef . current . destroy ( ) ;
45
56
peerRef . current = null ;
46
57
}
47
58
48
- // Disconnect socket
49
- if ( socketRef . current ) {
59
+ // Only disconnect socket if we're actually cleaning up (not remounting)
60
+ if ( force && socketRef . current ) {
50
61
socketRef . current . disconnect ( ) ;
51
62
socketRef . current = null ;
52
63
}
53
64
54
- // Stop and remove all audio elements
55
65
audioElementsRef . current . forEach ( ( audio ) => {
56
66
audio . pause ( ) ;
57
67
audio . srcObject = null ;
58
68
} ) ;
59
69
audioElementsRef . current = [ ] ;
60
70
61
- // Reset states
62
71
setIsAudioEnabled ( false ) ;
63
72
setConnectionStatus ( 'Not Connected' ) ;
64
- initializedRef . current = false ;
65
73
} ;
66
74
67
75
const createPeer = ( stream : MediaStream , initiator : boolean ) => {
@@ -86,42 +94,45 @@ const AudioSharing = () => {
86
94
} ) ;
87
95
88
96
peer . on ( 'signal' , ( data : SignalData ) => {
89
- console . log ( 'Sending signal data' ) ;
90
97
socketRef . current ?. emit ( 'signal' , data ) ;
91
98
} ) ;
92
99
93
100
peer . on ( 'stream' , ( remoteStream : MediaStream ) => {
94
101
console . log ( 'Received remote stream' ) ;
95
102
const audio = new Audio ( ) ;
96
103
audio . srcObject = remoteStream ;
97
- audioElementsRef . current . push ( audio ) ; // Track audio element for cleanup
104
+ audioElementsRef . current . push ( audio ) ;
98
105
audio
99
106
. play ( )
100
107
. catch ( ( error ) => console . error ( 'Error playing audio:' , error ) ) ;
101
108
} ) ;
102
109
103
110
peer . on ( 'error' , ( err : Error ) => {
104
111
console . error ( 'Peer connection error:' , err ) ;
105
- cleanupAudio ( ) ;
112
+ cleanupAudio ( true ) ;
106
113
} ) ;
107
114
108
115
peer . on ( 'close' , ( ) => {
109
116
console . log ( 'Peer connection closed' ) ;
110
- cleanupAudio ( ) ;
117
+ cleanupAudio ( true ) ;
111
118
} ) ;
112
119
113
120
peer . on ( 'connect' , ( ) => {
114
- console . log ( 'Peer connection established successfully ' ) ;
121
+ console . log ( 'Peer connection established' ) ;
115
122
setConnectionStatus ( 'Connected' ) ;
116
123
} ) ;
117
124
118
125
return peer ;
119
126
} ;
120
127
121
128
const initializeSocketAndPeer = ( ) => {
122
- if ( initializedRef . current ) return ;
123
- initializedRef . current = true ;
129
+ // If socket exists and is connected, reuse it
130
+ if ( socketRef . current ?. connected ) {
131
+ console . log ( 'Reusing existing socket connection' ) ;
132
+ return ;
133
+ }
124
134
135
+ console . log ( 'Initializing new socket connection' ) ;
125
136
socketRef . current = io ( SERVER_URL , {
126
137
transports : [ 'websocket' ] ,
127
138
path : '/socket.io/' ,
@@ -136,12 +147,10 @@ const AudioSharing = () => {
136
147
137
148
socketRef . current . on ( 'connect_error' , ( error : Error ) => {
138
149
console . error ( 'Connection error:' , error ) ;
139
- cleanupAudio ( ) ;
150
+ cleanupAudio ( true ) ;
140
151
} ) ;
141
152
142
153
socketRef . current . on ( 'signal' , async ( data : SignalData ) => {
143
- console . log ( 'Received signal data' ) ;
144
-
145
154
if ( data . type === 'offer' && ! peerRef . current ) {
146
155
try {
147
156
const stream = await navigator . mediaDevices . getUserMedia ( {
@@ -158,7 +167,7 @@ const AudioSharing = () => {
158
167
peerRef . current = createPeer ( stream , false ) ;
159
168
} catch ( error ) {
160
169
console . error ( 'Error accessing audio devices:' , error ) ;
161
- cleanupAudio ( ) ;
170
+ cleanupAudio ( true ) ;
162
171
}
163
172
}
164
173
@@ -167,7 +176,7 @@ const AudioSharing = () => {
167
176
peerRef . current . signal ( data as SimplePeer . SignalData ) ;
168
177
} catch ( error ) {
169
178
console . error ( 'Error signaling peer:' , error ) ;
170
- cleanupAudio ( ) ;
179
+ cleanupAudio ( true ) ;
171
180
}
172
181
}
173
182
} ) ;
@@ -202,27 +211,34 @@ const AudioSharing = () => {
202
211
}
203
212
} catch ( error ) {
204
213
console . error ( 'Error toggling audio:' , error ) ;
205
- cleanupAudio ( ) ;
214
+ cleanupAudio ( true ) ;
206
215
}
207
216
} ;
208
217
209
- // Cleanup effect when component unmounts
218
+ // Mount/unmount handling
210
219
useEffect ( ( ) => {
211
- // Cleanup function that will run when component unmounts
220
+ mountCountRef . current ++ ;
221
+ console . log ( `Component mounted (count: ${ mountCountRef . current } )` ) ;
222
+
223
+ // Only do full cleanup when actually leaving the page
212
224
return ( ) => {
213
- console . log ( 'AudioSharing component unmounting, cleaning up...' ) ;
214
- cleanupAudio ( ) ;
225
+ mountCountRef . current -- ;
226
+ console . log ( `Component unmounting (count: ${ mountCountRef . current } )` ) ;
227
+
228
+ // If this is the last mount point, do a full cleanup
229
+ if ( mountCountRef . current === 0 ) {
230
+ cleanupAudio ( true ) ;
231
+ }
215
232
} ;
216
233
} , [ ] ) ;
217
234
218
- // Add window unload handler to ensure cleanup even when page closes
235
+ // Handle page unload
219
236
useEffect ( ( ) => {
220
237
const handleUnload = ( ) => {
221
- cleanupAudio ( ) ;
238
+ cleanupAudio ( true ) ;
222
239
} ;
223
240
224
241
window . addEventListener ( 'beforeunload' , handleUnload ) ;
225
-
226
242
return ( ) => {
227
243
window . removeEventListener ( 'beforeunload' , handleUnload ) ;
228
244
} ;
0 commit comments