Skip to content

Commit 1d11e02

Browse files
committed
Code Rabbit fixes
1 parent 4af45ed commit 1d11e02

6 files changed

Lines changed: 142 additions & 67 deletions

File tree

src/components/Trackpad/ScreenMirror.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@ export const ScreenMirror = ({
3030
{/* Mirror Canvas */}
3131
<video
3232
ref={videoRef}
33+
aria-label="Screen Share Video"
3334
autoPlay
3435
playsInline
3536
muted
37+
onError={handleVideoError}
3638
className={`w-full h-full object-contain transition-opacity duration-500 ${
3739
hasStream ? "opacity-100" : "opacity-0"
3840
}`}
@@ -60,3 +62,7 @@ export const ScreenMirror = ({
6062
</div>
6163
)
6264
}
65+
66+
const handleVideoError = (e: React.SyntheticEvent<HTMLVideoElement, Event>) => {
67+
console.error("[RTC] Video playback error", e.currentTarget.error)
68+
}

src/contexts/ConnectionProvider.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface ConnectionContextType {
2121
cb: (stream: MediaStream | null) => void,
2222
) => () => void
2323
createPeerConnection: () => RTCPeerConnection
24+
closePeerConnection: () => void
2425
send: (msg: unknown) => void
2526
sendInput: (msg: unknown) => void
2627
configureMediaSender: (pc: RTCPeerConnection, sender: RTCRtpSender) => void
@@ -75,7 +76,7 @@ export function ConnectionProvider({
7576
}
7677
}, [])
7778

78-
const handleTrackReceived = useCallback((stream: MediaStream) => {
79+
const handleTrackReceived = useCallback((stream: MediaStream | null) => {
7980
for (const cb of mirrorStreamSubscribersRef.current) {
8081
cb(stream)
8182
}
@@ -87,6 +88,7 @@ export function ConnectionProvider({
8788
handleSignalingMessage,
8889
sendInput,
8990
configureMediaSender,
91+
closePeerConnection,
9092
} = useWebRTC(send, handleTrackReceived)
9193

9294
useEffect(() => {
@@ -266,6 +268,7 @@ export function ConnectionProvider({
266268
return () => mirrorStreamSubscribersRef.current.delete(cb)
267269
},
268270
createPeerConnection,
271+
closePeerConnection,
269272
send,
270273
sendInput,
271274
configureMediaSender,

src/hooks/useCaptureProvider.ts

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,20 @@ interface ElectronWindow extends Window {
1414
}
1515
}
1616

17+
interface ElectronDesktopConstraints {
18+
audio?: {
19+
mandatory: {
20+
chromeMediaSource: "desktop"
21+
}
22+
}
23+
video: {
24+
mandatory: {
25+
chromeMediaSource: "desktop"
26+
chromeMediaSourceId: string
27+
}
28+
}
29+
}
30+
1731
export function useCaptureProvider() {
1832
const {
1933
pcRef,
@@ -22,6 +36,7 @@ export function useCaptureProvider() {
2236
createPeerConnection,
2337
status,
2438
configureMediaSender,
39+
closePeerConnection,
2540
} = useConnection()
2641
const [isSharing, setIsSharing] = useState(false)
2742
const senderRef = useRef<RTCRtpSender | null>(null)
@@ -36,15 +51,14 @@ export function useCaptureProvider() {
3651
senderRef.current = null
3752
}
3853
if (pcRef.current) {
39-
pcRef.current.close()
40-
pcRef.current = null
54+
closePeerConnection()
4155
}
4256
if (streamRef.current) {
4357
for (const track of streamRef.current.getTracks()) track.stop()
4458
streamRef.current = null
4559
}
4660
setIsSharing(false)
47-
}, [pcRef])
61+
}, [pcRef, closePeerConnection])
4862

4963
const handleConsumerJoined = useCallback(async () => {
5064
if (isRequestingRef.current) return
@@ -55,21 +69,26 @@ export function useCaptureProvider() {
5569
isRequestingRef.current = true // Set lock
5670

5771
// ── Electron Native Sniffing Flow ──
58-
const electron = (window as unknown as ElectronWindow).electron
72+
const electron = (window as ElectronWindow).electron
5973
if (electron?.showSourcePicker) {
6074
const sourceId = await electron.showSourcePicker()
6175
if (sourceId) {
62-
stream = await navigator.mediaDevices.getUserMedia({
76+
const constraints: ElectronDesktopConstraints = {
6377
audio: {
64-
mandatory: { chromeMediaSource: "desktop" },
78+
mandatory: {
79+
chromeMediaSource: "desktop",
80+
},
6581
},
6682
video: {
6783
mandatory: {
6884
chromeMediaSource: "desktop",
6985
chromeMediaSourceId: sourceId,
7086
},
7187
},
72-
} as unknown as MediaStreamConstraints)
88+
}
89+
stream = await navigator.mediaDevices.getUserMedia(
90+
constraints as MediaStreamConstraints,
91+
)
7392
} else {
7493
// Fallback if dialog dismissed with no ID
7594
stream = await navigator.mediaDevices.getDisplayMedia({
@@ -86,10 +105,12 @@ export function useCaptureProvider() {
86105
}
87106

88107
streamRef.current = stream
89-
stream.getVideoTracks()[0].onended = () => stopSharing()
108+
stream
109+
.getVideoTracks()[0]
110+
?.addEventListener("ended", stopSharing, { once: true })
90111
}
91112

92-
if (pcRef.current) pcRef.current.close()
113+
if (pcRef.current) closePeerConnection()
93114
pcRef.current = createPeerConnection()
94115

95116
const dcUnordered = pcRef.current.createDataChannel("dc-unordered", {
@@ -103,6 +124,10 @@ export function useCaptureProvider() {
103124
const handleIncomingInput = (event: MessageEvent) => {
104125
try {
105126
const msg = JSON.parse(event.data)
127+
if (!msg || typeof msg !== "object" || !msg.type) {
128+
console.warn("[RTC] Invalid message format:", msg)
129+
return
130+
}
106131
send(msg)
107132
} catch (err) {
108133
console.error("[RTC] Failed to relay input", err)
@@ -127,7 +152,14 @@ export function useCaptureProvider() {
127152
} finally {
128153
isRequestingRef.current = false // Release lock
129154
}
130-
}, [createPeerConnection, pcRef, stopSharing, send, configureMediaSender])
155+
}, [
156+
createPeerConnection,
157+
closePeerConnection,
158+
pcRef,
159+
stopSharing,
160+
send,
161+
configureMediaSender,
162+
])
131163

132164
// Register as provider when WebSocket connects
133165
useEffect(() => {

src/hooks/useMirrorStream.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
* This hook handles subscribing to the incoming WebRTC media stream and binding
55
* it directly to a video element for display.
66
*/
7-
7+
import type { RefObject } from "react"
88
import { useConnection } from "@/contexts/ConnectionProvider"
99
import { useEffect, useRef, useState } from "react"
1010

1111
export function useMirrorStream(
12-
videoRef: React.RefObject<HTMLVideoElement | null>, // switch from canvas to video
12+
videoRef: RefObject<HTMLVideoElement | null>,
1313
status: "connecting" | "connected" | "disconnected",
1414
) {
1515
const { subscribeMirrorStream, send } = useConnection()
@@ -27,7 +27,6 @@ export function useMirrorStream(
2727
sendRef.current({ type: "start-mirror" })
2828

2929
const unsub = subRef.current((stream) => {
30-
console.log("[RTC] mirror-stream received")
3130
if (videoRef.current) {
3231
videoRef.current.srcObject = stream
3332
if (stream) {
@@ -41,6 +40,10 @@ export function useMirrorStream(
4140

4241
return () => {
4342
unsub()
43+
if (videoRef.current) {
44+
videoRef.current.srcObject = null
45+
}
46+
setHasStream(false)
4447
sendRef.current({ type: "stop-mirror" })
4548
}
4649
}, [status, videoRef])

src/hooks/useWebRTC.tsx

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type SignalingMessage =
1313

1414
export function useWebRTC(
1515
send: (msg: unknown) => void,
16-
onTrack: (stream: MediaStream) => void,
16+
onTrack: (stream: MediaStream | null) => void,
1717
) {
1818
const pcRef = useRef<RTCPeerConnection | null>(null)
1919
const dcUnorderedRef = useRef<RTCDataChannel | null>(null)
@@ -98,11 +98,26 @@ export function useWebRTC(
9898

9999
pc.onconnectionstatechange = () => {
100100
console.log("[RTC] Connection State:", pc.connectionState)
101+
if (
102+
pc.connectionState === "failed" ||
103+
pc.connectionState === "disconnected" ||
104+
pc.connectionState === "closed"
105+
) {
106+
onTrack(null)
107+
}
101108
}
102109

103110
pc.ontrack = (event) => {
104111
console.log("[RTC] track received")
105112
const stream = event.streams[0] ?? new MediaStream([event.track])
113+
event.track.addEventListener(
114+
"ended",
115+
() => {
116+
console.log("[RTC] track ended")
117+
onTrack(null)
118+
},
119+
{ once: true },
120+
)
106121
onTrack(stream)
107122
}
108123

@@ -130,41 +145,41 @@ export function useWebRTC(
130145

131146
const handleSignalingMessage = useCallback(
132147
async (msg: SignalingMessage) => {
133-
if (msg.type === "offer") {
134-
if (!pcRef.current) {
135-
pcRef.current = createPeerConnection()
136-
}
137-
const pc = pcRef.current
138-
await pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
139-
140-
for (const c of pendingIceCandidatesRef.current) {
141-
await pc.addIceCandidate(c)
142-
}
143-
pendingIceCandidatesRef.current = []
144-
145-
const answer = await pc.createAnswer()
146-
await pc.setLocalDescription(answer)
147-
sendRef.current({ type: "answer", sdp: pc.localDescription })
148-
} else if (msg.type === "answer") {
149-
const pc = pcRef.current
150-
if (pc) {
148+
try {
149+
if (msg.type === "offer") {
150+
if (!pcRef.current) {
151+
pcRef.current = createPeerConnection()
152+
}
153+
const pc = pcRef.current
151154
await pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
155+
152156
for (const c of pendingIceCandidatesRef.current) {
153157
await pc.addIceCandidate(c)
154158
}
155159
pendingIceCandidatesRef.current = []
156-
}
157-
} else if (msg.type === "ice-candidate") {
158-
const pc = pcRef.current
159-
if (pc?.remoteDescription) {
160-
try {
160+
161+
const answer = await pc.createAnswer()
162+
await pc.setLocalDescription(answer)
163+
sendRef.current({ type: "answer", sdp: pc.localDescription })
164+
} else if (msg.type === "answer") {
165+
const pc = pcRef.current
166+
if (pc) {
167+
await pc.setRemoteDescription(new RTCSessionDescription(msg.sdp))
168+
for (const c of pendingIceCandidatesRef.current) {
169+
await pc.addIceCandidate(c)
170+
}
171+
pendingIceCandidatesRef.current = []
172+
}
173+
} else if (msg.type === "ice-candidate") {
174+
const pc = pcRef.current
175+
if (pc?.remoteDescription) {
161176
await pc.addIceCandidate(new RTCIceCandidate(msg.candidate))
162-
} catch (e) {
163-
console.error("[RTC] Failed to add ICE candidate", e)
177+
} else {
178+
pendingIceCandidatesRef.current.push(msg.candidate)
164179
}
165-
} else {
166-
pendingIceCandidatesRef.current.push(msg.candidate)
167180
}
181+
} catch (e) {
182+
console.error("[RTC] Failed to handle signaling message", e)
168183
}
169184
},
170185
[createPeerConnection],
@@ -187,11 +202,23 @@ export function useWebRTC(
187202
}
188203
}, [])
189204

205+
const closePeerConnection = useCallback(() => {
206+
if (pcRef.current) {
207+
pcRef.current.close()
208+
pcRef.current = null
209+
}
210+
211+
dcUnorderedRef.current = null
212+
dcOrderedRef.current = null
213+
pendingIceCandidatesRef.current = []
214+
}, [])
215+
190216
return {
191217
pcRef,
192218
createPeerConnection,
193219
handleSignalingMessage,
194220
sendInput,
221+
closePeerConnection,
195222
configureMediaSender,
196223
}
197224
}

0 commit comments

Comments
 (0)