File size: 6,068 Bytes
abc1805
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import { useState, useEffect, useRef, useCallback } from "react";
import { socket } from "@/lib/socket";

const STUN_SERVERS = {
    iceServers: [
        { urls: "stun:stun.l.google.com:19302" },
        { urls: "stun:stun1.l.google.com:19302" },
    ],
};

export function useWebRTC(roomId: string) {
    const [localStream, setLocalStream] = useState<MediaStream | null>(null);
    const [remoteStreams, setRemoteStreams] = useState<Map<string, MediaStream>>(new Map());
    const peersRef = useRef<Map<string, RTCPeerConnection>>(new Map());

    const createPeer = useCallback((targetSocketId: string, initiator: boolean, stream: MediaStream) => {
        const peer = new RTCPeerConnection(STUN_SERVERS);
        peersRef.current.set(targetSocketId, peer);

        // Add local tracks
        stream.getTracks().forEach(track => peer.addTrack(track, stream));

        // Handle ICE candidates
        peer.onicecandidate = (event) => {
            if (event.candidate) {
                socket.emit("signal", {
                    target: targetSocketId,
                    signal: { type: "ice-candidate", candidate: event.candidate }
                });
            }
        };

        // Handle remote stream
        peer.ontrack = (event) => {
            console.log("Received remote track from:", targetSocketId);
            setRemoteStreams(prev => {
                const newMap = new Map(prev);
                newMap.set(targetSocketId, event.streams[0]);
                return newMap;
            });
        };

        // Create Offer if initiator
        if (initiator) {
            peer.createOffer()
                .then(offer => peer.setLocalDescription(offer))
                .then(() => {
                    socket.emit("signal", {
                        target: targetSocketId,
                        signal: { type: "offer", sdp: peer.localDescription }
                    });
                })
                .catch(err => console.error("Error creating offer:", err));
        }

        return peer;
    }, []);

    const joinCall = useCallback(async () => {
        try {
            const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
            setLocalStream(stream);
            socket.emit("join-call", { roomId });
            return stream;
        } catch (err) {
            console.error("Error accessing media devices:", err);
            alert("Could not access camera/microphone");
            return null;
        }
    }, [roomId]);

    const leaveCall = useCallback(() => {
        // Stop local stream
        if (localStream) {
            localStream.getTracks().forEach(track => track.stop());
            setLocalStream(null);
        }

        // Close all peers
        peersRef.current.forEach(peer => peer.close());
        peersRef.current.clear();
        setRemoteStreams(new Map());

        socket.emit("leave-call", { roomId });
    }, [localStream, roomId]);

    useEffect(() => {
        if (!localStream) return;

        const handleUserConnected = ({ socketId }: { socketId: string }) => {
            console.log("User connected to call:", socketId);
            // Initiate connection to new user
            createPeer(socketId, true, localStream);
        };

        const handleUserDisconnected = ({ socketId }: { socketId: string }) => {
            console.log("User disconnected from call:", socketId);
            if (peersRef.current.has(socketId)) {
                peersRef.current.get(socketId)!.close();
                peersRef.current.delete(socketId);
            }
            setRemoteStreams(prev => {
                const newMap = new Map(prev);
                newMap.delete(socketId);
                return newMap;
            });
        };

        const handleSignal = async (data: { sender: string; signal: any }) => {
            const { sender, signal } = data;

            // Ignore key exchange signals (offer-key, answer-key)
            if (signal.type === "offer-key" || signal.type === "answer-key") return;

            let peer = peersRef.current.get(sender);

            if (!peer) {
                // If receiving offer, create peer (not initiator)
                if (signal.type === "offer") {
                    peer = createPeer(sender, false, localStream);
                } else {
                    console.warn("Received signal for unknown peer:", sender);
                    return;
                }
            }

            try {
                if (signal.type === "offer") {
                    await peer.setRemoteDescription(new RTCSessionDescription(signal.sdp));
                    const answer = await peer.createAnswer();
                    await peer.setLocalDescription(answer);
                    socket.emit("signal", {
                        target: sender,
                        signal: { type: "answer", sdp: peer.localDescription }
                    });
                } else if (signal.type === "answer") {
                    await peer.setRemoteDescription(new RTCSessionDescription(signal.sdp));
                } else if (signal.type === "ice-candidate") {
                    await peer.addIceCandidate(new RTCIceCandidate(signal.candidate));
                }
            } catch (err) {
                console.error("Error handling signal:", err);
            }
        };

        socket.on("user-connected-to-call", handleUserConnected);
        socket.on("user-disconnected-from-call", handleUserDisconnected);
        socket.on("signal", handleSignal);

        return () => {
            socket.off("user-connected-to-call", handleUserConnected);
            socket.off("user-disconnected-from-call", handleUserDisconnected);
            socket.off("signal", handleSignal);
        };
    }, [localStream, createPeer]);

    return { localStream, remoteStreams, joinCall, leaveCall };
}