1
1
'use client' ;
2
2
3
- import React , { useRef , useState } from 'react' ;
3
+ import React , { useRef , useState , useEffect } from 'react' ;
4
4
import io , { Socket } from 'socket.io-client' ;
5
5
import SimplePeer , { Instance } from 'simple-peer' ;
6
6
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' ;
@@ -15,38 +15,59 @@ 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 ) ;
23
+ const audioElementsRef = useRef < HTMLAudioElement [ ] > ( [ ] ) ;
24
+ const mountCountRef = useRef ( 0 ) ;
25
+ const lastCleanupTimeRef = useRef ( 0 ) ;
22
26
27
+ // Constants
28
+ const CLEANUP_THRESHOLD = 1000 ; // Minimum time between cleanups in ms
23
29
const SERVER_URL =
24
30
process . env . NEXT_PUBLIC_AUDIO_SERVER_URL || 'http://localhost:5555' ;
25
-
26
- // Add TURN server credentials from environment variables
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
- if ( ! TURN_SERVER || ! TURN_USERNAME || ! TURN_CREDENTIAL ) {
32
- // Log which specific TURN variables are missing
33
- console . error ( 'Missing TURN env:' , {
34
- server : ! ! TURN_SERVER ,
35
- username : ! ! TURN_USERNAME ,
36
- credential : ! ! TURN_CREDENTIAL ,
37
- } ) ;
38
- }
39
- 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
+
44
+ console . log ( 'Cleaning up audio connections...' ) ;
45
+ lastCleanupTimeRef . current = now ;
46
+
40
47
if ( audioStreamRef . current ) {
41
48
audioStreamRef . current . getTracks ( ) . forEach ( ( track ) => {
42
49
track . stop ( ) ;
43
50
} ) ;
44
51
audioStreamRef . current = null ;
45
52
}
53
+
46
54
if ( peerRef . current ) {
47
55
peerRef . current . destroy ( ) ;
48
56
peerRef . current = null ;
49
57
}
58
+
59
+ // Only disconnect socket if we're actually cleaning up (not remounting)
60
+ if ( force && socketRef . current ) {
61
+ socketRef . current . disconnect ( ) ;
62
+ socketRef . current = null ;
63
+ }
64
+
65
+ audioElementsRef . current . forEach ( ( audio ) => {
66
+ audio . pause ( ) ;
67
+ audio . srcObject = null ;
68
+ } ) ;
69
+ audioElementsRef . current = [ ] ;
70
+
50
71
setIsAudioEnabled ( false ) ;
51
72
setConnectionStatus ( 'Not Connected' ) ;
52
73
} ;
@@ -61,10 +82,8 @@ const AudioSharing = () => {
61
82
trickle : false ,
62
83
config : {
63
84
iceServers : [
64
- // Maintain existing STUN servers
65
85
{ urls : 'stun:stun.l.google.com:19302' } ,
66
86
{ urls : 'stun:global.stun.twilio.com:3478' } ,
67
- // Add TURN server configuration
68
87
{
69
88
urls : TURN_SERVER ,
70
89
username : TURN_USERNAME ,
@@ -75,42 +94,45 @@ const AudioSharing = () => {
75
94
} ) ;
76
95
77
96
peer . on ( 'signal' , ( data : SignalData ) => {
78
- console . log ( 'Sending signal data:' , data ) ;
79
97
socketRef . current ?. emit ( 'signal' , data ) ;
80
98
} ) ;
81
99
82
100
peer . on ( 'stream' , ( remoteStream : MediaStream ) => {
83
101
console . log ( 'Received remote stream' ) ;
84
102
const audio = new Audio ( ) ;
85
103
audio . srcObject = remoteStream ;
104
+ audioElementsRef . current . push ( audio ) ;
86
105
audio
87
106
. play ( )
88
107
. catch ( ( error ) => console . error ( 'Error playing audio:' , error ) ) ;
89
108
} ) ;
90
109
91
110
peer . on ( 'error' , ( err : Error ) => {
92
111
console . error ( 'Peer connection error:' , err ) ;
93
- cleanupAudio ( ) ;
112
+ cleanupAudio ( true ) ;
94
113
} ) ;
95
114
96
115
peer . on ( 'close' , ( ) => {
97
116
console . log ( 'Peer connection closed' ) ;
98
- cleanupAudio ( ) ;
117
+ cleanupAudio ( true ) ;
99
118
} ) ;
100
119
101
- // Add connection state logging
102
120
peer . on ( 'connect' , ( ) => {
103
- console . log ( 'Peer connection established successfully ' ) ;
121
+ console . log ( 'Peer connection established' ) ;
104
122
setConnectionStatus ( 'Connected' ) ;
105
123
} ) ;
106
124
107
125
return peer ;
108
126
} ;
109
127
110
128
const initializeSocketAndPeer = ( ) => {
111
- if ( initializedRef . current ) return ;
112
- 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
+ }
113
134
135
+ console . log ( 'Initializing new socket connection' ) ;
114
136
socketRef . current = io ( SERVER_URL , {
115
137
transports : [ 'websocket' ] ,
116
138
path : '/socket.io/' ,
@@ -125,12 +147,10 @@ const AudioSharing = () => {
125
147
126
148
socketRef . current . on ( 'connect_error' , ( error : Error ) => {
127
149
console . error ( 'Connection error:' , error ) ;
128
- cleanupAudio ( ) ;
150
+ cleanupAudio ( true ) ;
129
151
} ) ;
130
152
131
153
socketRef . current . on ( 'signal' , async ( data : SignalData ) => {
132
- console . log ( 'Received signal data:' , data ) ;
133
-
134
154
if ( data . type === 'offer' && ! peerRef . current ) {
135
155
try {
136
156
const stream = await navigator . mediaDevices . getUserMedia ( {
@@ -147,7 +167,7 @@ const AudioSharing = () => {
147
167
peerRef . current = createPeer ( stream , false ) ;
148
168
} catch ( error ) {
149
169
console . error ( 'Error accessing audio devices:' , error ) ;
150
- cleanupAudio ( ) ;
170
+ cleanupAudio ( true ) ;
151
171
}
152
172
}
153
173
@@ -156,7 +176,7 @@ const AudioSharing = () => {
156
176
peerRef . current . signal ( data as SimplePeer . SignalData ) ;
157
177
} catch ( error ) {
158
178
console . error ( 'Error signaling peer:' , error ) ;
159
- cleanupAudio ( ) ;
179
+ cleanupAudio ( true ) ;
160
180
}
161
181
}
162
182
} ) ;
@@ -191,10 +211,39 @@ const AudioSharing = () => {
191
211
}
192
212
} catch ( error ) {
193
213
console . error ( 'Error toggling audio:' , error ) ;
194
- cleanupAudio ( ) ;
214
+ cleanupAudio ( true ) ;
195
215
}
196
216
} ;
197
217
218
+ // Mount/unmount handling
219
+ useEffect ( ( ) => {
220
+ mountCountRef . current ++ ;
221
+ console . log ( `Component mounted (count: ${ mountCountRef . current } )` ) ;
222
+
223
+ // Only do full cleanup when actually leaving the page
224
+ return ( ) => {
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
+ }
232
+ } ;
233
+ } , [ ] ) ;
234
+
235
+ // Handle page unload
236
+ useEffect ( ( ) => {
237
+ const handleUnload = ( ) => {
238
+ cleanupAudio ( true ) ;
239
+ } ;
240
+
241
+ window . addEventListener ( 'beforeunload' , handleUnload ) ;
242
+ return ( ) => {
243
+ window . removeEventListener ( 'beforeunload' , handleUnload ) ;
244
+ } ;
245
+ } , [ ] ) ;
246
+
198
247
return (
199
248
< div className = "flex items-center gap-4" >
200
249
< button
0 commit comments