Skip to content

Commit 4cb5892

Browse files
committed
chpt 5
1 parent f4139b5 commit 4cb5892

1 file changed

Lines changed: 78 additions & 12 deletions

File tree

src/hooks/useWebRtcStream.ts

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,31 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
3333
}
3434
}, [])
3535

36+
const checkServerActive = useCallback(async (): Promise<boolean> => {
37+
try {
38+
const headers: Record<string, string> = {}
39+
if (token) {
40+
headers.Authorization = `Bearer ${token}`
41+
}
42+
const controller = new AbortController()
43+
const timeoutId = setTimeout(() => controller.abort(), 2000)
44+
const response = await fetch("/api/host/status", {
45+
headers,
46+
signal: controller.signal,
47+
})
48+
clearTimeout(timeoutId)
49+
return response.ok
50+
} catch {
51+
return false
52+
}
53+
}, [token])
54+
3655
const triggerRetry = useCallback(() => {
37-
if (trackActiveRef.current || retryTimerRef.current) return
56+
if (retryTimerRef.current) return
57+
58+
setTrackActive(false)
59+
setVideoStream(null)
60+
3861
console.log(
3962
"[WebRTC] Startup/transient network failure, retrying automatically in 2 seconds...",
4063
)
@@ -45,6 +68,15 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
4568
}, 2000)
4669
}, [])
4770

71+
const handleNetworkFailure = useCallback(async () => {
72+
const isServerOnline = await checkServerActive()
73+
if (isServerOnline) {
74+
triggerRetry()
75+
} else {
76+
setError("Server has quit or is unreachable")
77+
}
78+
}, [checkServerActive, triggerRetry])
79+
4880
const reconnect = () => {
4981
if (retryTimerRef.current) {
5082
clearTimeout(retryTimerRef.current)
@@ -84,9 +116,9 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
84116
})
85117
.catch((err) => {
86118
console.error("[WebRTC] Session init failed:", err)
87-
triggerRetry()
119+
handleNetworkFailure()
88120
})
89-
}, [token, reconnectAttempt, triggerRetry])
121+
}, [token, reconnectAttempt, handleNetworkFailure])
90122

91123
useEffect(() => {
92124
if (!activeSessionId) return
@@ -123,7 +155,7 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
123155
}),
124156
}).catch((err) => {
125157
console.error(err)
126-
triggerRetry()
158+
handleNetworkFailure()
127159
})
128160
}
129161

@@ -132,7 +164,7 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
132164
videoPc.connectionState === "failed" ||
133165
videoPc.connectionState === "disconnected"
134166
) {
135-
triggerRetry()
167+
handleNetworkFailure()
136168
}
137169
}
138170

@@ -166,7 +198,7 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
166198
}),
167199
}).catch((err) => {
168200
console.error(err)
169-
triggerRetry()
201+
handleNetworkFailure()
170202
})
171203
}
172204

@@ -175,7 +207,7 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
175207
inputPc.connectionState === "failed" ||
176208
inputPc.connectionState === "disconnected"
177209
) {
178-
triggerRetry()
210+
handleNetworkFailure()
179211
}
180212
}
181213

@@ -191,7 +223,7 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
191223

192224
sse.onerror = (event) => {
193225
console.error("[WebRTC] SSE error:", event)
194-
triggerRetry()
226+
handleNetworkFailure()
195227
}
196228

197229
sse.addEventListener("stream-error", (event) => {
@@ -232,7 +264,7 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
232264
}
233265
} catch (err) {
234266
console.error("[WebRTC] Video offer handling failed:", err)
235-
triggerRetry()
267+
handleNetworkFailure()
236268
}
237269
})
238270

@@ -278,7 +310,7 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
278310
}
279311
} catch (err) {
280312
console.error("[WebRTC] Input answer failed:", err)
281-
triggerRetry()
313+
handleNetworkFailure()
282314
}
283315
})
284316

@@ -319,17 +351,51 @@ export function useWebRtcStream({ token }: UseWebRtcStreamOptions) {
319351

320352
sendInputOffer().catch((err) => {
321353
console.error(err)
322-
triggerRetry()
354+
handleNetworkFailure()
323355
})
324356

357+
// Video stream watchdog to automatically recover from silent stream freezes
358+
let lastBytesReceived = 0
359+
let lastBytesTime = Date.now()
360+
361+
const statsInterval = setInterval(async () => {
362+
if (!trackActiveRef.current) return
363+
try {
364+
const stats = await videoPc.getStats()
365+
let videoInbound = null
366+
for (const report of stats.values()) {
367+
if (report.type === "inbound-rtp" && report.kind === "video") {
368+
videoInbound = report
369+
break
370+
}
371+
}
372+
if (videoInbound) {
373+
const bytes = videoInbound.bytesReceived
374+
const now = Date.now()
375+
if (bytes > lastBytesReceived) {
376+
lastBytesReceived = bytes
377+
lastBytesTime = now
378+
} else if (now - lastBytesTime > 4000) {
379+
console.warn(
380+
"[WebRTC] Video stream freeze detected (no bytes received for 4s), reconnecting...",
381+
)
382+
handleNetworkFailure()
383+
}
384+
}
385+
} catch (err) {
386+
console.error("[WebRTC] Failed to fetch stats:", err)
387+
}
388+
}, 2000)
389+
325390
return () => {
391+
clearInterval(statsInterval)
326392
sse.close()
327393
videoPc.close()
328394
inputPc.close()
329395
setTrackActive(false)
330396
setVideoStream(null)
331397
}
332-
}, [activeSessionId, token, registerDataChannel, triggerRetry])
398+
}, [activeSessionId, token, registerDataChannel, handleNetworkFailure])
333399

334400
return { trackActive, videoStream, error, reconnect, sendInputEvent }
335401
}

0 commit comments

Comments
 (0)