2.3.4.2 Client Logic (React Example)
// frontend/src/features/voice/VoiceChat.jsx
import React, { useRef, useEffect, useState } from 'react';
import { getSocket } from '../../services/socket';
function VoiceChat({ targetUserId }) {
const localVideoRef = useRef(null);
const remoteVideoRef = useRef(null);
const pcRef = useRef(null);
const [socket, setSocket] = useState(null);
useEffect(() => {
const s = getSocket();
setSocket(s);
pcRef.current = new RTCPeerConnection();
// On receiving new ICE candidate
s.on('iceCandidate', ({ fromUserId, candidate }) => {
pcRef.current.addIceCandidate(new RTCIceCandidate(candidate));
});
// On receiving an offer
s.on('offer', async ({ fromUserId, sdp }) => {
await pcRef.current.setRemoteDescription(new RTCSessionDescription(sdp));
const answer = await pcRef.current.createAnswer();
await pcRef.current.setLocalDescription(answer);
s.emit('answer', { toUserId: fromUserId, sdp: answer });
});
// On receiving an answer
s.on('answer', async ({ fromUserId, sdp }) => {
await pcRef.current.setRemoteDescription(new RTCSessionDescription(sdp));
});
// ICE candidate generation
pcRef.current.onicecandidate = (event) => {
if (event.candidate) {
s.emit('iceCandidate', {
toUserId: targetUserId,
candidate: event.candidate
});
}
};
// Streams
pcRef.current.ontrack = (event) => {
remoteVideoRef.current.srcObject = event.streams[0];
};
// Get user media
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((stream) => {
localVideoRef.current.srcObject = stream;
stream.getTracks().forEach((track) => {
pcRef.current.addTrack(track, stream);
});
})
.catch(console.error);
}, [targetUserId]);
const initiateCall = async () => {
const offer = await pcRef.current.createOffer();
await pcRef.current.setLocalDescription(offer);
socket.emit('offer', { toUserId: targetUserId, sdp: offer });
};
return (
<div>
<video ref={localVideoRef} autoPlay muted />
<video ref={remoteVideoRef} autoPlay />
<button onClick={initiateCall}>Call</button>
</div>
);
}
export default VoiceChat;
Note:
• In production, we’ll need TURN servers (e.g., coturn) for relaying media when direct P2P fails.
• Additional features like group calls require more complex logic or a media server (e.g., Jitsi, Janus).
Last updated