198 lines
8.3 KiB
TypeScript
198 lines
8.3 KiB
TypeScript
|
|
'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>
|
||
|
|
)
|
||
|
|
}
|