CoffeeChat/webapp/components/chat/WebRTComponent.tsx

198 lines
8.3 KiB
TypeScript
Raw Permalink Normal View History

2026-04-03 12:35:13 +02:00
'use client'
/* eslint-disable @typescript-eslint/no-explicit-any */
import React, { useEffect, useRef, useState } from 'react'
// https://www.baeldung.com/webrtc
export default function WebRTComponent() {
const WSRef = useRef<WebSocket>(undefined)
const videoRef1 = useRef<HTMLVideoElement | null>(null)
const localStreamRef1 = useRef<MediaStream | null>(null)
const [offers, setOffers] = useState<RTCSessionDescriptionInit[]>([])
const peerConnection = useRef<RTCPeerConnection>(undefined)
const dataChannel = useRef<RTCDataChannel>(undefined)
const send = (message: any) =>
WSRef.current!.send(JSON.stringify(message));
const onWSMessage = async (message: MessageEvent) => {
console.log(`Received : `, message.data)
const parsed = JSON.parse(message.data)
switch (parsed.type) {
case 'Offers':
// setOffers(o => parsed.data as RTCSessionDescriptionInit[])
console.log('parsed.data',parsed.data)
await acceptOffer(parsed.data)
// await acceptOffer(parsed.data[parsed.data.length - 1])
break;
case 'Answers':
console.log(`Answers:`,parsed.data)
peerConnection.current!
.setRemoteDescription(new RTCSessionDescription({ ...parsed.data.sdp }))
.catch((err) => console.error(err)); //
break;
}
}
const handleConectWS = async () => {
WSRef.current = new WebSocket('ws://localhost:4000/socket');
WSRef.current.onopen = async function (event) {
peerConnection.current = new RTCPeerConnection(undefined) // <-- Later add Stun servers as backup
await setup()
}
WSRef.current.onmessage = async (ev) => onWSMessage(ev)
}
// 1. Get user media
const Start = async () => {
console.log('Requesting local stream');
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
console.log(`Start Local Stream`)
if (!videoRef1.current) console.error(`Video not yet available`)
videoRef1.current!.srcObject = stream
localStreamRef1.current = stream;
console.log(`Set local stream`)
}
const joinServer = async () => {
const configuration = {};
console.log('RTCPeerConnection configuration:', configuration);
peerConnection.current = new RTCPeerConnection(configuration)
peerConnection.current.createOffer().then(offer => {
send({
type: 'OfferSDP',
data: offer
})
peerConnection.current?.setLocalDescription(offer)
})
peerConnection.current.onicecandidate = function (event) {
console.log(`Received ICE Candidate :`, event)
}
// pc.addEventListener('icecandidate', e => console.log(`icecandidate`, pc, e));
}
const acceptOffer = async (recv: any) => {
console.log(`acceptOffer`, recv)
peerConnection.current!.setRemoteDescription(new RTCSessionDescription(recv))
.then(() => peerConnection.current!.createAnswer())
.then((answer) => peerConnection.current!.setLocalDescription(answer))
.then(() => send({ type: 'AnswerSDP', data: { sdp: peerConnection.current!.localDescription } }))
.catch((err) => console.error('Failed to handle offer', err));
}
// 2. Make call
const makeCall = () => {
// Get available streams
const videoTracks = localStreamRef1.current!.getVideoTracks();
const audioTracks = localStreamRef1.current!.getAudioTracks();
if (videoTracks.length > 0) {
console.log(`Using video device: ${videoTracks[0].label}`);
}
if (audioTracks.length > 0) {
console.log(`Using audio device: ${audioTracks[0].label}`);
}
const configuration = {};
// 3. Start WebRTC
console.log('RTCPeerConnection configuration:', configuration);
// const pc1 = new RTCPeerConnection(configuration);
// console.log('Created local peer connection object pc1');
// pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));
// const pc2 = new RTCPeerConnection(configuration);
// console.log('Created remote peer connection object pc2');
// pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));
// pc1.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc1, e));
// pc2.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc2, e));
// pc2.addEventListener('track', gotRemoteStream);
}
// 4. On ICE Candidate
// const onIceCandidate = async (peerConnection: RTCPeerConnection, e: Event) => {
// try {
// await(getOtherPc(pc).addIceCandidate(event.candidate));
// onAddIceCandidateSuccess(pc);
// } catch (e) {
// onAddIceCandidateError(pc, e);
// }
// console.log(`${getName(pc)} ICE candidate:\n${event.candidate ? event.candidate.candidate : '(null)'}`);
// }
async function setup() {
// 1. Get local media stream
// await Start()
// 2. Join WS Server
const handleJoin = () => {
send({ type: 'Join' })
}
handleJoin()
}
useEffect(() => {
handleConectWS()
// const load = async () => {
// WSRef.current.onmessage = (ev) => {
// // '{"event":"candidate","data":{"candidate":"candidate:1041930304 1 udp 2113937151 b7178aee-2d1d-4762-b709-1b25cf436cb5.local 61551 typ host generation 0 ufrag bEe6 network-cost 999","sdpMid":"0","sdpMLineIndex":0,"usernameFragment":"bEe6"}}'
// const data = JSON.parse(ev.data)
// // console.log(`Recevied WS : `, Object.keys(data))
// console.log(`Recevied WS : `, data.event)
// switch (data.event) {
// case 'offer':
// console.log(`Check foreign offer`, data.data)
// // Need to handle answer here
// peerConnection.current.setRemoteDescription(new RTCSessionDescription({ sdp: data.data.sdp, type: 'offer' }))
// .then(() => peerConnection.current.createAnswer())
// .then((answer) => peerConnection.current.setLocalDescription(answer))
// .then(() => send({ event: 'answer', data: { sdp: peerConnection.current.localDescription } }))
// .catch((err) => console.error('Failed to handle offer', err));
// break;
// case 'answer':
// console.log(`Check foreign answer`, data.data)
// peerConnection.current.setRemoteDescription(new RTCSessionDescription({ ...data.data.sdp })).catch((err) => console.error(err)); //
// break;
// case 'candidate':
// console.log(`Check foreign candidate`, { ...data.data })
// if (data.data.candidate) {
// peerConnection.current.addIceCandidate(new RTCIceCandidate({ ...data.data })).catch((err) => console.warn('Bad candidate', err));
// }
// break;
// }
// }
// }
// load()
}, [])
return (
<div>WebRTCChat
<video ref={videoRef1} playsInline autoPlay muted className='border w-96 h-96 bg-red-100' />
<button onClick={() => Start()}>Start</button>
<button onClick={() => joinServer()}>Join</button>
<button onClick={() => makeCall()}>Make Call</button>
<button onClick={() => {
{ alert('SEND'); dataChannel.current!.send("message"); }
}} >Send</button>
<div>
<b>Offers</b>
{/* <div className='flex flex-col'>
{offers.map((o, i) => <div key={`Offer-${i}`}>
{JSON.stringify(o.sdp)}
<button onClick={() => {
acceptOffer(o.sdp ?? "")
}}>Connect</button>
</div>)}
</div> */}
</div>
</div>
)
}