narinder1231 commited on
Commit
e586605
·
1 Parent(s): 3533a0f

fix useWebRTC hook to handle events correctly

Browse files
Files changed (1) hide show
  1. src/hooks/useWebRTC.ts +98 -91
src/hooks/useWebRTC.ts CHANGED
@@ -1,133 +1,140 @@
1
- import { useState } from "react";
2
 
3
  import { ICreateConversationResponse } from "../interfaces/conversation";
4
- import { createConversation, createConnection } from "../services/api/chatService";
5
  import { conversationWebSocket } from "../services/websockets/conversation";
6
 
7
- export function useWebRTC() {
8
- const [peerConnection, setPeerConnection] = useState<RTCPeerConnection | null>(null);
9
- const [socket, setSocket] = useState<WebSocket | null>(null);
10
- const [transcript, setTranscript] = useState<string>("");
11
  const [isConnected, setIsConnected] = useState(false);
 
12
  const [remoteStream, setRemoteStream] = useState<MediaStream | null>(null);
 
 
 
 
13
  const [error, setError] = useState<string | null>(null);
 
 
14
 
15
- async function startCall() {
16
- try {
17
- setError(null);
18
- const sessionResponse = await createConversation({ modality: "voice" });
19
- const sessionData: ICreateConversationResponse = await sessionResponse.data;
20
- const conversationId = sessionData.conversation_id;
21
 
22
- const pc = new RTCPeerConnection();
23
- setPeerConnection(pc);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- pc.ontrack = (event) => {
26
- const newStream = new MediaStream();
27
- newStream.addTrack(event.track);
28
- setRemoteStream(newStream);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  };
30
 
31
- pc.onconnectionstatechange = () => {
32
- if (pc.connectionState === "connected") {
33
- setIsConnected(true);
 
 
 
 
 
34
  }
35
  };
36
 
37
- const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: false });
38
- stream.getTracks().forEach((track) => {
39
- pc.addTrack(track, stream);
40
- });
41
-
42
- const offer = await pc.createOffer();
43
- await pc.setLocalDescription(offer);
44
 
 
 
45
  const webrtcResponse = await createConnection(conversationId, {
46
  conversation_id: conversationId,
47
  offer: { sdp: offer.sdp, type: offer.type },
48
  });
49
 
50
- const response = await webrtcResponse.data;
51
- const ephemeralKey = response.ephemeral_key;
52
-
53
- const ws = conversationWebSocket({
54
  conversationId,
55
  modality: "voice",
56
  });
57
 
58
- setSocket(ws);
59
-
60
- ws.onopen = () => {
61
- const auth_token = localStorage.getItem("auth_token");
62
- const token = `Bearer ${auth_token}`;
63
- ws.send(
64
  JSON.stringify({
65
  type: "headers",
66
- headers: {
67
- Authorization: `Bearer ${ephemeralKey}`,
68
- Token: token,
69
- },
70
  })
71
  );
72
  };
73
 
74
- ws.onmessage = (event) => {
75
  try {
76
  const data = JSON.parse(event.data);
77
  if (data.transcript) {
78
- setTranscript(data.transcript);
 
 
79
  }
80
  } catch {
81
- setError("Error processing message from server");
82
- }
83
- };
84
-
85
- pc.onicecandidate = (event) => {
86
- if (event.candidate && ws.readyState === WebSocket.OPEN) {
87
- ws.send(
88
- JSON.stringify({
89
- type: "ice-candidate",
90
- candidate: event.candidate,
91
- })
92
- );
93
  }
94
  };
95
 
96
- ws.onclose = (_event) => {
97
- if (isConnected) {
98
- endCall();
99
- }
100
  };
101
-
102
- await pc.setRemoteDescription(new RTCSessionDescription(response.answer));
103
-
104
- const dataChannel = pc.createDataChannel("chat");
105
- dataChannel.onmessage = (event) => {
106
- if (ws && ws.readyState === WebSocket.OPEN) {
107
- ws.send(event.data);
108
- }
109
- };
110
- } catch (error) {
111
- setError(error instanceof Error ? error.message : "Failed to start call");
112
  endCall();
113
- throw error;
114
- }
115
- }
116
-
117
- function endCall() {
118
- if (peerConnection) {
119
- peerConnection.close();
120
- setPeerConnection(null);
121
- }
122
-
123
- if (socket) {
124
- socket.close();
125
- setSocket(null);
126
  }
127
-
128
- setIsConnected(false);
129
- setRemoteStream(null);
130
- }
131
-
132
- return { startCall, endCall, transcript, isConnected, remoteStream, error };
133
- }
 
 
 
 
 
 
1
+ import { useState, useRef, useCallback, useEffect } from "react";
2
 
3
  import { ICreateConversationResponse } from "../interfaces/conversation";
4
+ import { createConnection, createConversation } from "../services/api/chatService";
5
  import { conversationWebSocket } from "../services/websockets/conversation";
6
 
7
+ export const useWebRTC = () => {
 
 
 
8
  const [isConnected, setIsConnected] = useState(false);
9
+ const [transcript, setTranscript] = useState("");
10
  const [remoteStream, setRemoteStream] = useState<MediaStream | null>(null);
11
+ const [callStatus, setCallStatus] = useState("");
12
+ const peerConnectionRef = useRef<RTCPeerConnection | null>(null);
13
+ const dataChannelRef = useRef<RTCDataChannel | null>(null);
14
+ const socketRef = useRef<WebSocket | null>(null);
15
  const [error, setError] = useState<string | null>(null);
16
+ const mediaStreamRef = useRef<MediaStream | null>(null);
17
+ const isConnectedRef = useRef(isConnected);
18
 
19
+ useEffect(() => {
20
+ isConnectedRef.current = isConnected;
21
+ }, [isConnected]);
 
 
 
22
 
23
+ const endCall = useCallback(() => {
24
+ if (peerConnectionRef.current) {
25
+ peerConnectionRef.current.close();
26
+ peerConnectionRef.current = null;
27
+ }
28
+ if (socketRef.current) {
29
+ socketRef.current.close();
30
+ socketRef.current = null;
31
+ }
32
+ if (mediaStreamRef.current) {
33
+ mediaStreamRef.current.getTracks().forEach((track) => track.stop());
34
+ mediaStreamRef.current = null;
35
+ }
36
+ if (dataChannelRef.current) {
37
+ dataChannelRef.current.close();
38
+ dataChannelRef.current = null;
39
+ }
40
+ setIsConnected(false);
41
+ setCallStatus("Disconnected");
42
+ setRemoteStream(null);
43
+ setTranscript("");
44
+ }, []);
45
 
46
+ const startCall = useCallback(async () => {
47
+ try {
48
+ const peerConnection = new RTCPeerConnection();
49
+ peerConnectionRef.current = peerConnection;
50
+ peerConnection.onconnectionstatechange = () => {
51
+ switch (peerConnection.connectionState) {
52
+ case "connected":
53
+ setIsConnected(true);
54
+ setCallStatus("Connected");
55
+ break;
56
+ case "disconnected":
57
+ case "failed":
58
+ case "closed":
59
+ endCall();
60
+ break;
61
+ default:
62
+ break;
63
+ }
64
+ };
65
+ peerConnection.ontrack = (event) => {
66
+ setRemoteStream(event.streams[0]);
67
  };
68
 
69
+ const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
70
+ mediaStreamRef.current = mediaStream;
71
+ peerConnection.addTrack(mediaStream.getTracks()[0], mediaStream);
72
+ const dataChannel = peerConnection.createDataChannel("response");
73
+ dataChannelRef.current = dataChannel;
74
+ dataChannel.onmessage = (event) => {
75
+ if (socketRef.current?.readyState === WebSocket.OPEN) {
76
+ socketRef.current.send(event.data);
77
  }
78
  };
79
 
80
+ const sessionResponse = await createConversation({ modality: "voice" });
81
+ const sessionData: ICreateConversationResponse = await sessionResponse.data;
82
+ const conversationId = sessionData.conversation_id;
 
 
 
 
83
 
84
+ const offer = await peerConnection.createOffer();
85
+ await peerConnection.setLocalDescription(offer);
86
  const webrtcResponse = await createConnection(conversationId, {
87
  conversation_id: conversationId,
88
  offer: { sdp: offer.sdp, type: offer.type },
89
  });
90
 
91
+ const responseData = await webrtcResponse.data;
92
+ const ephemeralKey = responseData.ephemeral_key;
93
+ const socket = conversationWebSocket({
 
94
  conversationId,
95
  modality: "voice",
96
  });
97
 
98
+ socketRef.current = socket;
99
+ socket.onopen = () => {
100
+ socket.send(
 
 
 
101
  JSON.stringify({
102
  type: "headers",
103
+ headers: { Authorization: `Bearer ${ephemeralKey}` },
 
 
 
104
  })
105
  );
106
  };
107
 
108
+ socket.onmessage = (event) => {
109
  try {
110
  const data = JSON.parse(event.data);
111
  if (data.transcript) {
112
+ setTranscript((prev) => `${prev}\n${data.transcript}`);
113
+ } else if (dataChannelRef.current?.readyState === "open") {
114
+ dataChannelRef.current.send(JSON.stringify(data));
115
  }
116
  } catch {
117
+ setError("Error handling message");
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
  };
120
 
121
+ socket.onclose = (_event) => {
122
+ if (isConnectedRef.current) endCall();
 
 
123
  };
124
+ await peerConnection.setRemoteDescription(new RTCSessionDescription(responseData.answer));
125
+ } catch {
126
+ setError("Call setup failed");
 
 
 
 
 
 
 
 
127
  endCall();
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  }
129
+ }, [endCall]);
130
+
131
+ return {
132
+ startCall,
133
+ endCall,
134
+ isConnected,
135
+ transcript,
136
+ remoteStream,
137
+ callStatus,
138
+ error,
139
+ };
140
+ };