Add Webapp
This commit is contained in:
commit
ed924ef2ac
76 files changed
+17054
No files matched your search
@@ -0,0 +1,33 @@
|
||||
import React from 'react'
|
||||
import { Input } from '../ui/input'
|
||||
import { InputGroup, InputGroupAddon, InputGroupButton } from '../ui/input-group'
|
||||
import { Textarea } from '../ui/textarea'
|
||||
|
||||
export default function ChatBox() {
|
||||
return (
|
||||
<div className='flex flex-col'>
|
||||
<div className='grow flex flex-col'>
|
||||
CHAT MESSAGES
|
||||
<div className='p-1 rounded-2xl border max-w-1/2 self-start'>Placeholder message</div>
|
||||
<div className='p-1 rounded-2xl border max-w-1/2 self-end'>Placeholder message</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="grid w-full max-w-sm gap-6">
|
||||
<InputGroup>
|
||||
<Textarea
|
||||
data-slot="input-group-control"
|
||||
className="flex field-sizing-content min-h-16 w-full resize-none rounded-md bg-transparent px-3 py-2.5 text-base transition-[color,box-shadow] outline-none md:text-sm"
|
||||
placeholder="Message..."
|
||||
/>
|
||||
<InputGroupAddon align="block-end">
|
||||
<InputGroupButton className="ml-auto" size="sm" variant="default">
|
||||
Send
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import React, { useRef } from 'react'
|
||||
|
||||
export default function ChatNoServerWidget() {
|
||||
const localVideo = useRef<HTMLVideoElement | null>(null)
|
||||
const remoteVideo = useRef<HTMLVideoElement | null>(null)
|
||||
const localStream = useRef<MediaStream>(null)
|
||||
const pc1 = useRef<RTCPeerConnection>(null)
|
||||
const pc2 = useRef<RTCPeerConnection>(null)
|
||||
|
||||
|
||||
const handleStart = async () => {
|
||||
console.log('Requesting local stream');
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
||||
console.log('Received local stream');
|
||||
localVideo.current!.srcObject = stream;
|
||||
localStream.current = stream;
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert(`getUserMedia() error: ${(e as Error).name}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getName(pc: RTCPeerConnection) {
|
||||
return (pc === pc1.current) ? 'pc1' : 'pc2';
|
||||
}
|
||||
|
||||
function getOtherPc(pc: RTCPeerConnection) {
|
||||
return (pc === pc1.current) ? pc2.current : pc1.current;
|
||||
}
|
||||
|
||||
async function onIceCandidate(pc: RTCPeerConnection, event: RTCPeerConnectionIceEvent) {
|
||||
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)'}`);
|
||||
}
|
||||
function onAddIceCandidateSuccess(pc: RTCPeerConnection) {
|
||||
console.log(`${getName(pc)} addIceCandidate success`);
|
||||
}
|
||||
|
||||
function onAddIceCandidateError(pc: RTCPeerConnection, error: any) {
|
||||
console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
|
||||
}
|
||||
|
||||
function onIceStateChange(pc: RTCPeerConnection, event: Event) {
|
||||
if (pc) {
|
||||
console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);
|
||||
console.log('ICE state change event: ', event);
|
||||
}
|
||||
}
|
||||
|
||||
function gotRemoteStream(e: RTCTrackEvent) {
|
||||
if (remoteVideo.current!.srcObject !== e.streams[0]) {
|
||||
remoteVideo.current!.srcObject = e.streams[0];
|
||||
console.log('pc2 received remote stream');
|
||||
}
|
||||
}
|
||||
async function call() {
|
||||
console.log('Starting call');
|
||||
|
||||
const videoTracks = localStream.current!.getVideoTracks();
|
||||
const audioTracks = localStream.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 = {};
|
||||
console.log('RTCPeerConnection configuration:', configuration);
|
||||
pc1.current = new RTCPeerConnection(configuration);
|
||||
console.log('Created local peer connection object pc1');
|
||||
pc1.current.addEventListener('icecandidate', e => onIceCandidate(pc1.current!, e));
|
||||
pc2.current = new RTCPeerConnection(configuration);
|
||||
console.log('Created remote peer connection object pc2');
|
||||
pc2.current.addEventListener('icecandidate', e => onIceCandidate(pc2.current!, e));
|
||||
pc1.current.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc1.current!, e));
|
||||
pc2.current.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc2.current!, e));
|
||||
pc2.current.addEventListener('track', gotRemoteStream);
|
||||
|
||||
localStream.current!.getTracks().forEach(track => pc1.current!.addTrack(track, localStream.current!));
|
||||
console.log('Added local stream to pc1');
|
||||
|
||||
try {
|
||||
console.log('pc1 createOffer start');
|
||||
const offer = await pc1.current.createOffer();
|
||||
// const offer = await pc1.current.createOffer(offerOptions);
|
||||
await onCreateOfferSuccess(offer);
|
||||
} catch (e) {
|
||||
onCreateSessionDescriptionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
function onSetLocalSuccess(pc: RTCPeerConnection) {
|
||||
console.log(`${getName(pc)} setLocalDescription complete`);
|
||||
}
|
||||
|
||||
function onSetRemoteSuccess(pc: RTCPeerConnection) {
|
||||
console.log(`${getName(pc)} setRemoteDescription complete`);
|
||||
}
|
||||
|
||||
function onSetSessionDescriptionError(error: any) {
|
||||
console.log(`Failed to set session description: ${error.toString()}`);
|
||||
}
|
||||
|
||||
async function onCreateOfferSuccess(desc: RTCSessionDescriptionInit) {
|
||||
console.log(`Offer from pc1\n${desc.sdp}`);
|
||||
console.log('pc1 setLocalDescription start');
|
||||
try {
|
||||
await pc1.current!.setLocalDescription(desc);
|
||||
onSetLocalSuccess(pc1.current!);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError(e);
|
||||
}
|
||||
|
||||
console.log('pc2 setRemoteDescription start');
|
||||
try {
|
||||
await pc2.current!.setRemoteDescription(desc);
|
||||
onSetRemoteSuccess(pc2.current!);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError(e);
|
||||
}
|
||||
|
||||
console.log('pc2 createAnswer start');
|
||||
// Since the 'remote' side has no media stream we need
|
||||
// to pass in the right constraints in order for it to
|
||||
// accept the incoming offer of audio and video.
|
||||
try {
|
||||
const answer = await pc2.current!.createAnswer();
|
||||
await onCreateAnswerSuccess(answer);
|
||||
} catch (e) {
|
||||
onCreateSessionDescriptionError(e);
|
||||
}
|
||||
}
|
||||
async function onCreateAnswerSuccess(desc: RTCSessionDescriptionInit) {
|
||||
console.log(`Answer from pc2:\n${desc.sdp}`);
|
||||
console.log('pc2 setLocalDescription start');
|
||||
try {
|
||||
await pc2.current!.setLocalDescription(desc);
|
||||
onSetLocalSuccess(pc2.current!);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError(e);
|
||||
}
|
||||
console.log('pc1 setRemoteDescription start');
|
||||
try {
|
||||
await pc1.current!.setRemoteDescription(desc);
|
||||
onSetRemoteSuccess(pc1.current!);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
function onCreateSessionDescriptionError(error: any) {
|
||||
console.log(`Failed to create session description: ${error.toString()}`);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<video ref={localVideo} id="localVideo" playsInline autoPlay muted></video>
|
||||
<video ref={remoteVideo} id="remoteVideo" playsInline autoPlay></video>
|
||||
|
||||
<div className="box">
|
||||
<button onClick={handleStart} id="startButton">Start</button>
|
||||
<button onClick={() => call()} id="callButton">Call</button>
|
||||
<button id="hangupButton">Hang Up</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import React, { useRef } from 'react'
|
||||
|
||||
export default function ChatNoServerWidget() {
|
||||
const localVideo = useRef<HTMLVideoElement | null>(null)
|
||||
const remoteVideo = useRef<HTMLVideoElement | null>(null)
|
||||
const localStream = useRef<MediaStream>(null)
|
||||
const pc1 = useRef<RTCPeerConnection>(null)
|
||||
const pc2 = useRef<RTCPeerConnection>(null)
|
||||
|
||||
|
||||
const handleStart = async () => {
|
||||
console.log('Requesting local stream');
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
||||
console.log('Received local stream');
|
||||
localVideo.current!.srcObject = stream;
|
||||
localStream.current = stream;
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
alert(`getUserMedia() error: ${(e as Error).name}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getName(pc: RTCPeerConnection) {
|
||||
return (pc === pc1.current) ? 'pc1' : 'pc2';
|
||||
}
|
||||
|
||||
function getOtherPc(pc: RTCPeerConnection) {
|
||||
return (pc === pc1.current) ? pc2.current : pc1.current;
|
||||
}
|
||||
|
||||
async function onIceCandidate(pc: RTCPeerConnection, event: RTCPeerConnectionIceEvent) {
|
||||
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)'}`);
|
||||
}
|
||||
function onAddIceCandidateSuccess(pc: RTCPeerConnection) {
|
||||
console.log(`${getName(pc)} addIceCandidate success`);
|
||||
}
|
||||
|
||||
function onAddIceCandidateError(pc: RTCPeerConnection, error: any) {
|
||||
console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
|
||||
}
|
||||
|
||||
function onIceStateChange(pc: RTCPeerConnection, event: Event) {
|
||||
if (pc) {
|
||||
console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);
|
||||
console.log('ICE state change event: ', event);
|
||||
}
|
||||
}
|
||||
|
||||
function gotRemoteStream(e: RTCTrackEvent) {
|
||||
if (remoteVideo.current!.srcObject !== e.streams[0]) {
|
||||
remoteVideo.current!.srcObject = e.streams[0];
|
||||
console.log('pc2 received remote stream');
|
||||
}
|
||||
}
|
||||
async function call() {
|
||||
console.log('Starting call');
|
||||
|
||||
const videoTracks = localStream.current!.getVideoTracks();
|
||||
const audioTracks = localStream.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 = {};
|
||||
console.log('RTCPeerConnection configuration:', configuration);
|
||||
pc1.current = new RTCPeerConnection(configuration);
|
||||
console.log('Created local peer connection object pc1');
|
||||
pc1.current.addEventListener('icecandidate', e => onIceCandidate(pc1.current!, e));
|
||||
pc2.current = new RTCPeerConnection(configuration);
|
||||
console.log('Created remote peer connection object pc2');
|
||||
pc2.current.addEventListener('icecandidate', e => onIceCandidate(pc2.current!, e));
|
||||
pc1.current.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc1.current!, e));
|
||||
pc2.current.addEventListener('iceconnectionstatechange', e => onIceStateChange(pc2.current!, e));
|
||||
pc2.current.addEventListener('track', gotRemoteStream);
|
||||
|
||||
localStream.current!.getTracks().forEach(track => pc1.current!.addTrack(track, localStream.current!));
|
||||
console.log('Added local stream to pc1');
|
||||
|
||||
try {
|
||||
console.log('pc1 createOffer start');
|
||||
const offer = await pc1.current.createOffer();
|
||||
// const offer = await pc1.current.createOffer(offerOptions);
|
||||
await onCreateOfferSuccess(offer);
|
||||
} catch (e) {
|
||||
onCreateSessionDescriptionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
function onSetLocalSuccess(pc: RTCPeerConnection) {
|
||||
console.log(`${getName(pc)} setLocalDescription complete`);
|
||||
}
|
||||
|
||||
function onSetRemoteSuccess(pc: RTCPeerConnection) {
|
||||
console.log(`${getName(pc)} setRemoteDescription complete`);
|
||||
}
|
||||
|
||||
function onSetSessionDescriptionError(error: any) {
|
||||
console.log(`Failed to set session description: ${error.toString()}`);
|
||||
}
|
||||
|
||||
async function onCreateOfferSuccess(desc: RTCSessionDescriptionInit) {
|
||||
console.log(`Offer from pc1\n${desc.sdp}`);
|
||||
console.log('pc1 setLocalDescription start');
|
||||
try {
|
||||
await pc1.current!.setLocalDescription(desc);
|
||||
onSetLocalSuccess(pc1.current!);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError(e);
|
||||
}
|
||||
|
||||
console.log('pc2 setRemoteDescription start');
|
||||
try {
|
||||
await pc2.current!.setRemoteDescription(desc);
|
||||
onSetRemoteSuccess(pc2.current!);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError(e);
|
||||
}
|
||||
|
||||
console.log('pc2 createAnswer start');
|
||||
// Since the 'remote' side has no media stream we need
|
||||
// to pass in the right constraints in order for it to
|
||||
// accept the incoming offer of audio and video.
|
||||
try {
|
||||
const answer = await pc2.current!.createAnswer();
|
||||
await onCreateAnswerSuccess(answer);
|
||||
} catch (e) {
|
||||
onCreateSessionDescriptionError(e);
|
||||
}
|
||||
}
|
||||
async function onCreateAnswerSuccess(desc: RTCSessionDescriptionInit) {
|
||||
console.log(`Answer from pc2:\n${desc.sdp}`);
|
||||
console.log('pc2 setLocalDescription start');
|
||||
try {
|
||||
await pc2.current!.setLocalDescription(desc);
|
||||
onSetLocalSuccess(pc2.current!);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError(e);
|
||||
}
|
||||
console.log('pc1 setRemoteDescription start');
|
||||
try {
|
||||
await pc1.current!.setRemoteDescription(desc);
|
||||
onSetRemoteSuccess(pc1.current!);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
function onCreateSessionDescriptionError(error: any) {
|
||||
console.log(`Failed to create session description: ${error.toString()}`);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<video ref={localVideo} id="localVideo" playsInline autoPlay muted></video>
|
||||
<video ref={remoteVideo} id="remoteVideo" playsInline autoPlay></video>
|
||||
|
||||
<div className="box">
|
||||
<button onClick={handleStart} id="startButton">Start</button>
|
||||
<button onClick={() => call()} id="callButton">Call</button>
|
||||
<button id="hangupButton">Hang Up</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
'use client'
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { Socket } from 'socket.io';
|
||||
import { io } from 'socket.io-client';
|
||||
import { socket } from './socket';
|
||||
import Link from 'next/link';
|
||||
|
||||
// https://www.baeldung.com/webrtc
|
||||
export default function ChatWS() {
|
||||
|
||||
const [session, setSession] = useState<{
|
||||
who: string,
|
||||
room?: string
|
||||
}>()
|
||||
const [messages, setMessages] = useState<{
|
||||
who: string,
|
||||
msg: string
|
||||
}[]>([])
|
||||
const nameRef = useRef<HTMLInputElement | null>(null)
|
||||
useEffect(() => {
|
||||
socket.onAny((ev, data) => {
|
||||
console.log(`ANY`, ev, data)
|
||||
// setMessages(prevMessages => [...prevMessages, data]);
|
||||
});
|
||||
socket.on('chat message', (data) => {
|
||||
// console.log(`chat message`,data)
|
||||
setMessages(prevMessages => [...prevMessages, data]);
|
||||
});
|
||||
|
||||
// Clean up the listener when the component unmounts
|
||||
return () => {
|
||||
socket.off('chat message');
|
||||
};
|
||||
}, [])
|
||||
|
||||
if (!session?.who) return <div
|
||||
style={{ 'background': 'conic-gradient(from 210deg, #c6bcb9 0.000deg, #c6bcb9 24.000deg, #b9b6b9 calc(24.000deg + 0.1deg), #b9b6b9 48.000deg, #aab0b8 calc(48.000deg + 0.1deg), #aab0b8 72.000deg, #9ba9b6 calc(72.000deg + 0.1deg), #9ba9b6 96.000deg, #8ca2b4 calc(96.000deg + 0.1deg), #8ca2b4 120.000deg, #7e99b0 calc(120.000deg + 0.1deg), #7e99b0 144.000deg, #7191ac calc(144.000deg + 0.1deg), #7191ac 168.000deg, #6688a7 calc(168.000deg + 0.1deg), #6688a7 192.000deg, #5d7fa2 calc(192.000deg + 0.1deg), #5d7fa2 216.000deg, #56769c calc(216.000deg + 0.1deg), #56769c 240.000deg, #526d95 calc(240.000deg + 0.1deg), #526d95 264.000deg, #50648e calc(264.000deg + 0.1deg), #50648e 288.000deg, #515b86 calc(288.000deg + 0.1deg), #515b86 312.000deg, #55547e calc(312.000deg + 0.1deg), #55547e 336.000deg, #5c4c75 calc(336.000deg + 0.1deg) 360.000deg)' }}
|
||||
className='bg-gray-100 h-svh w-screen z-30 fixed flex items-center justify-center align-middle'>
|
||||
<div
|
||||
// onSubmit={ev => {
|
||||
// ev.preventDefault()
|
||||
// const form = new FormData(ev.target)
|
||||
// alert(form.get(`name`))
|
||||
// setSession((s: any) => {
|
||||
// return {
|
||||
// ...s,
|
||||
// who: form.get(`name`)
|
||||
// }
|
||||
// })
|
||||
// }}
|
||||
className='bg-white/10 backdrop-blur-lg w-96 h-64 flex flex-col border border-white/20 shadow-lg p-2'>
|
||||
<div className='grow font-black text-center'>
|
||||
Enter a name to use in this chat
|
||||
</div>
|
||||
<div className='grow'>
|
||||
<input
|
||||
ref={nameRef}
|
||||
className='w-full focus:p-2 transition-all focus:bg-white'
|
||||
autoFocus
|
||||
name='name'
|
||||
placeholder='Your name' />
|
||||
</div>
|
||||
<button type='button'
|
||||
onClick={() => {
|
||||
setSession((s: any) => {
|
||||
return {
|
||||
...s,
|
||||
who: nameRef.current?.value
|
||||
}
|
||||
})
|
||||
}} className='shadow p-2'>Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
return (
|
||||
<div
|
||||
/* https://grabient.com/HQNgrAHANMYIxhgJjAFigWlgTkVsaM2AzHJsMRAAzlKrTBwhJRwVgv4gjnYDsfcsWzpQSYq2BVpUKkA?style=angularSwatches&angle=210&steps=15 */
|
||||
|
||||
// background: conic-gradient(from 210deg, #7c6f6f 0.000deg, #7c6f6f 24.000deg, #6e6b6d calc(24.000deg + 0.1deg), #6e6b6d 48.000deg, #5f666a calc(48.000deg + 0.1deg), #5f666a 72.000deg, #505f67 calc(72.000deg + 0.1deg), #505f67 96.000deg, #3f5862 calc(96.000deg + 0.1deg), #3f5862 120.000deg, #2f515e calc(120.000deg + 0.1deg), #2f515e 144.000deg, #204858 calc(144.000deg + 0.1deg), #204858 168.000deg, #123f52 calc(168.000deg + 0.1deg), #123f52 192.000deg, #05364b calc(192.000deg + 0.1deg), #05364b 216.000deg, #002c43 calc(216.000deg + 0.1deg), #002c43 240.000deg, #00233b calc(240.000deg + 0.1deg), #00233b 264.000deg, #001933 calc(264.000deg + 0.1deg), #001933 288.000deg, #000f2a calc(288.000deg + 0.1deg), #000f2a 312.000deg, #000621 calc(312.000deg + 0.1deg), #000621 336.000deg, #000017 calc(336.000deg + 0.1deg) 360.000deg);
|
||||
style={{ 'background': 'black' }}
|
||||
// style={{ 'background': 'conic-gradient(from 210deg, #c6bcb9 0.000deg, #c6bcb9 24.000deg, #b9b6b9 calc(24.000deg + 0.1deg), #b9b6b9 48.000deg, #aab0b8 calc(48.000deg + 0.1deg), #aab0b8 72.000deg, #9ba9b6 calc(72.000deg + 0.1deg), #9ba9b6 96.000deg, #8ca2b4 calc(96.000deg + 0.1deg), #8ca2b4 120.000deg, #7e99b0 calc(120.000deg + 0.1deg), #7e99b0 144.000deg, #7191ac calc(144.000deg + 0.1deg), #7191ac 168.000deg, #6688a7 calc(168.000deg + 0.1deg), #6688a7 192.000deg, #5d7fa2 calc(192.000deg + 0.1deg), #5d7fa2 216.000deg, #56769c calc(216.000deg + 0.1deg), #56769c 240.000deg, #526d95 calc(240.000deg + 0.1deg), #526d95 264.000deg, #50648e calc(264.000deg + 0.1deg), #50648e 288.000deg, #515b86 calc(288.000deg + 0.1deg), #515b86 312.000deg, #55547e calc(312.000deg + 0.1deg), #55547e 336.000deg, #5c4c75 calc(336.000deg + 0.1deg) 360.000deg)' }}
|
||||
// style={{ 'background': 'conic-gradient(from 210deg, #7c6f6f 0.000deg, #7c6f6f 24.000deg, #6e6b6d calc(24.000deg + 0.1deg), #6e6b6d 48.000deg, #5f666a calc(48.000deg + 0.1deg), #5f666a 72.000deg, #505f67 calc(72.000deg + 0.1deg), #505f67 96.000deg, #3f5862 calc(96.000deg + 0.1deg), #3f5862 120.000deg, #2f515e calc(120.000deg + 0.1deg), #2f515e 144.000deg, #204858 calc(144.000deg + 0.1deg), #204858 168.000deg, #123f52 calc(168.000deg + 0.1deg), #123f52 192.000deg, #05364b calc(192.000deg + 0.1deg), #05364b 216.000deg, #002c43 calc(216.000deg + 0.1deg), #002c43 240.000deg, #00233b calc(240.000deg + 0.1deg), #00233b 264.000deg, #001933 calc(264.000deg + 0.1deg), #001933 288.000deg, #000f2a calc(288.000deg + 0.1deg), #000f2a 312.000deg, #000621 calc(312.000deg + 0.1deg), #000621 336.000deg, #000017 calc(336.000deg + 0.1deg) 360.000deg)' }}
|
||||
|
||||
className='flex flex-col h-svh w-screen'>
|
||||
<div className='bg-purple-900 shadow-sm shadow-purple-800'>
|
||||
<Link href={`/`}>
|
||||
<button className='bg-purple-950 text-white p-2 shadow hover:bg-purple-700'>Close Chat</button>
|
||||
</Link>
|
||||
</div>
|
||||
<div className='grow flex flex-col overflow-auto gap-2'>
|
||||
{messages.map((msg, i) =>
|
||||
<div
|
||||
className={`w-1/2 flex flex-col bg-gray-100 p-2 rounded-2xl shadow ${msg.who === session.who ? 'self-start' : 'self-end'}`}
|
||||
key={`message-${i}`}>
|
||||
<small>{msg.who}</small>
|
||||
{msg.msg}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* {JSON.stringify(messages)} */}
|
||||
|
||||
|
||||
<div className='text-center text-amber-600 bg-amber-100'>
|
||||
All message are removed once you leave this chat
|
||||
</div>
|
||||
<form onSubmit={ev => {
|
||||
ev.preventDefault()
|
||||
const form = new FormData(ev.target)
|
||||
if (!form.get('msg')) return
|
||||
socket.emit('chat message', {
|
||||
who: session!.who ?? 'Annonymous',
|
||||
msg: form.get('msg') as string
|
||||
})
|
||||
ev.currentTarget.reset()
|
||||
}}
|
||||
className='flex p-2 bg-gray-100 shadow-2xl opacity-90'
|
||||
>
|
||||
<input placeholder='Message' name='msg' className='grow' />
|
||||
<button
|
||||
className='shadow p-2 bg-gray-300'
|
||||
onClick={() => {
|
||||
// socket.emit('chat message', {
|
||||
// who: 'Clint',
|
||||
// msg: `Hello`
|
||||
// })
|
||||
// socket.send(`Hello MSG`)
|
||||
}
|
||||
}>Send</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
|
||||
import { Device } from 'mediasoup-client'
|
||||
import { Transport } from "mediasoup-client/types"
|
||||
|
||||
const wsURL = 'ws://localhost:4000'
|
||||
let remoteStream
|
||||
export default function MediaSoupWidget() {
|
||||
|
||||
const socket = useRef<WebSocket | undefined>(undefined)
|
||||
const device = useRef<Device | undefined>(undefined)
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null)
|
||||
const videoBRef = useRef<HTMLVideoElement | null>(null)
|
||||
const consumerTransport = useRef<Transport | null>(null)
|
||||
const [connected, setConnected] = useState<boolean>(false)
|
||||
const [states, setStates] = useState<string[]>([])
|
||||
|
||||
const loadDevices = async (routerCapabilities: any) => {
|
||||
try {
|
||||
device.current = new Device()
|
||||
await device.current!.load({ routerRtpCapabilities: routerCapabilities })
|
||||
// console.log(`Supported : `, device)
|
||||
setConnected(true)
|
||||
} catch (e) {
|
||||
if ((e as any).name === 'UnsupportedErro') {
|
||||
console.log(`Not Supported`)
|
||||
}
|
||||
console.error(`Faield to create device:`, e)
|
||||
}
|
||||
}
|
||||
|
||||
const getUserMedia = async (transport: any, isWebCam: boolean) => {
|
||||
if (!device.current?.canProduce('video')) {
|
||||
console.error(`Cant stream video`)
|
||||
return
|
||||
}
|
||||
let stream
|
||||
try {
|
||||
stream = isWebCam ? await navigator.mediaDevices.getUserMedia({
|
||||
video: true, audio: true
|
||||
}) : await navigator.mediaDevices.getDisplayMedia({ video: true })
|
||||
} catch (e) {
|
||||
console.error(`Unable to get device to stream`, e)
|
||||
}
|
||||
return stream
|
||||
}
|
||||
|
||||
const onProducerTransportCreated = async (producerDetails: any) => {
|
||||
// 5. Createe Send Transport
|
||||
|
||||
setStates(s => [...s, 'createSendTransport'])
|
||||
const transport = device.current?.createSendTransport({ ...producerDetails })
|
||||
transport?.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
||||
// 6. connectProducerTransport
|
||||
setStates(s => [...s, 'createSendTransport'])
|
||||
socket.current?.send(JSON.stringify({
|
||||
type: 'connectProducerTransport',
|
||||
dtlsParameters
|
||||
}))
|
||||
|
||||
socket.current?.addEventListener('message', ev => {
|
||||
// 7. producerTransportConnected
|
||||
// console.log(`SEOM TO CHK `, ev.data)
|
||||
if (JSON.parse(ev.data).type === 'producerTransportConnected') {
|
||||
setStates(s => [...s, 'producerTransportConnected'])
|
||||
// console.log(`------->producerTransportConnected:onCB`)
|
||||
callback()
|
||||
}
|
||||
})
|
||||
})
|
||||
transport?.on('produce', async ({ kind, rtpParameters }, callback, errback) => {
|
||||
// 8. Produce
|
||||
setStates(s => [...s, 'produce'])
|
||||
socket.current?.send(JSON.stringify({
|
||||
type: 'produce',
|
||||
transportId: transport.id,
|
||||
kind,
|
||||
rtpParameters
|
||||
}))
|
||||
socket.current?.addEventListener('message', ev => {
|
||||
if (ev.data.type === 'published') {
|
||||
callback(ev.data.id``)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
transport?.on('connectionstatechange', async (state) => {
|
||||
console.log(`connectionstatechange`, state)
|
||||
switch (state) {
|
||||
case 'connecting': break;
|
||||
case 'closed': break;
|
||||
case 'connected':
|
||||
// Link stream here
|
||||
break;
|
||||
case 'disconnected': break;
|
||||
case 'failed':
|
||||
transport.close()
|
||||
break;
|
||||
case 'new': break;
|
||||
}
|
||||
})
|
||||
transport?.on('icecandidateerror', async (error) => {
|
||||
console.log('icecandidateerror', error)
|
||||
})
|
||||
transport?.on('icegatheringstatechange', async (error) => {
|
||||
console.log('icegatheringstatechange', error)
|
||||
})
|
||||
transport?.on('producedata', async (error) => {
|
||||
console.log('producedata', error)
|
||||
})
|
||||
|
||||
let streamMedia
|
||||
try {
|
||||
streamMedia = await getUserMedia(transport, true) // is webcam
|
||||
const track = streamMedia!.getVideoTracks()[0]
|
||||
// const params - { track }
|
||||
// videoRef.current?.h = track
|
||||
console.log(`LocalStream`, streamMedia)
|
||||
// videoRef.current!.srcObject = streamMedia
|
||||
|
||||
const producer = await transport?.produce({ track })
|
||||
producer!.on("trackended", () => {
|
||||
console.log("track ended");
|
||||
});
|
||||
producer!.on("transportclose", () => {
|
||||
console.log("transport ended");
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
console.log(`ERROR`, e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const onSubTransportCreated = (consumerDetails: any) => {
|
||||
consumerTransport.current = device.current!.createRecvTransport({ ...consumerDetails })
|
||||
|
||||
console.log(`onSubTransportCreated`, consumerDetails, consumerTransport.current.connectionState)
|
||||
consumerTransport.current.on('connect', ({ dtlsParameters }, callback, errback) => {
|
||||
//11 . Accept connect
|
||||
|
||||
setStates(s => [...s, 'connectConsumerTransport'])
|
||||
socket.current?.send(JSON.stringify({
|
||||
type: 'connectConsumerTransport',
|
||||
transportId: consumerTransport.current!.id,
|
||||
dtlsParameters
|
||||
}))
|
||||
socket.current?.addEventListener('message', ev => {
|
||||
if (JSON.parse(ev.data).type === 'subConnected') {
|
||||
console.log(`subConnected**`, JSON.parse(ev.data).type)
|
||||
callback()
|
||||
}
|
||||
})
|
||||
})
|
||||
consumerTransport.current?.on('connectionstatechange', async (state) => {
|
||||
console.log(`connectionstatechange:`, state)
|
||||
switch (state) {
|
||||
case 'connecting':
|
||||
|
||||
break;
|
||||
case 'failed': console.log(`FILAED`); break;
|
||||
case 'connected':
|
||||
console.log(`remoteStream`, remoteStream)
|
||||
// videoBRef.current!.srcObject = remoteStream
|
||||
|
||||
socket.current?.send(JSON.stringify({
|
||||
type: 'resume',
|
||||
}))
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
})
|
||||
consumerTransport.current?.on('icecandidateerror', () => {
|
||||
console.log(`icecandidateerror`)
|
||||
})
|
||||
consumerTransport.current?.on('icegatheringstatechange', async (state) => {
|
||||
console.log(`icegatheringstatechange`, state)
|
||||
})
|
||||
|
||||
const stream = consumer(consumerTransport.current)
|
||||
}
|
||||
|
||||
const onSubscribe = async (details: any) => {
|
||||
console.log('onSubscribe', details)
|
||||
const {
|
||||
producerId,
|
||||
id,
|
||||
kind,
|
||||
rtpParameters,
|
||||
type,
|
||||
producerPaused,
|
||||
} = details
|
||||
|
||||
const codecOptions = {}
|
||||
const consumer = await consumerTransport.current!.consume({
|
||||
producerId,
|
||||
id,
|
||||
kind,
|
||||
rtpParameters,
|
||||
// type,
|
||||
// producerPaused,
|
||||
// codecOptions
|
||||
})
|
||||
const stream = new MediaStream()
|
||||
stream.addTrack(consumer.track)
|
||||
console.log(`TRYIN STREAM 1`,stream)
|
||||
videoBRef.current!.srcObject = stream
|
||||
}
|
||||
|
||||
const consumer = async (transport: any) => {
|
||||
const rtpCapabilities = device.current?.recvRtpCapabilities
|
||||
socket.current?.send(JSON.stringify({
|
||||
type: 'consume',
|
||||
rtpCapabilities
|
||||
}))
|
||||
}
|
||||
// const connectSendTransport = async () => {
|
||||
// const producer = await transport.produce(params);
|
||||
// console.log("Producer created:", producer.id, producer.kind);
|
||||
// producer.on("trackended", () => {
|
||||
// console.log("track ended");
|
||||
// });
|
||||
// producer.on("transportclose", () => {
|
||||
// console.log("transport ended");
|
||||
// });
|
||||
// };
|
||||
|
||||
const stream = () => {
|
||||
// 4.1 Start creating producers steam
|
||||
|
||||
// 4. Producers stream
|
||||
setStates(s => [...s, 'createProducerTransport'])
|
||||
socket.current?.send(JSON.stringify({
|
||||
type: 'createProducerTransport',
|
||||
forceTcp: false,
|
||||
rtpCapabilities: device.current?.sendRtpCapabilities
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
const joinStream = () => {
|
||||
setStates(s => [...s, 'createConsumerTransport'])
|
||||
socket.current?.send(JSON.stringify({
|
||||
type: 'createConsumerTransport',
|
||||
forceTcp: false,
|
||||
// rtpCapabilities: device.current?.sendRtpCapabilities
|
||||
}))
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const parseWSMessage = (ev: any) => {
|
||||
const recv = JSON.parse(ev)
|
||||
// console.log(`-- parseWSMessage --`, recv.data)
|
||||
switch (recv.type) {
|
||||
case 'routerCapabilities':
|
||||
// 3. Received capabilities
|
||||
setStates(s => [...s, 'routerCapabilities'])
|
||||
loadDevices(recv.data);
|
||||
break;
|
||||
case 'producerTransportCreated':
|
||||
// 4.2 Received capabilities
|
||||
setStates(s => [...s, 'producerTransportCreated'])
|
||||
onProducerTransportCreated(recv.data);
|
||||
break;
|
||||
// case 'producerTransportConnected':
|
||||
// // 7. producerTransportConnected but not with callback
|
||||
// setStates(s => [...s, 'producerTransportCreated'])
|
||||
// // callback()
|
||||
// break;
|
||||
case 'newProducer':
|
||||
// 9 Found new Produce contents and send to all clients
|
||||
setStates(s => [...s, 'newProducer'])
|
||||
break;
|
||||
case 'subTransportCreated':
|
||||
// 10 Consumer joined
|
||||
setStates(s => [...s, 'subTransportCreated'])
|
||||
onSubTransportCreated(recv.data);
|
||||
break;
|
||||
case 'resumed':
|
||||
// 10 Consumer joined
|
||||
setStates(s => [...s, 'resumed'])
|
||||
console.log(`resumed`, recv.data)
|
||||
break;
|
||||
case 'subscribed':
|
||||
// 12 Consumer joined
|
||||
setStates(s => [...s, 'subscribed'])
|
||||
onSubscribe(recv.data)
|
||||
break;
|
||||
default:
|
||||
console.log(`Received Uknown`, recv.type)
|
||||
|
||||
break;
|
||||
}
|
||||
// console.log(ev)
|
||||
|
||||
}
|
||||
return (
|
||||
<div>WebRTCChat
|
||||
<div>
|
||||
<div className={`${states.includes('ws-connected') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Connected To Server</div>
|
||||
<small>Establish connection to the WS Server</small>
|
||||
</div>
|
||||
<div className={`${states.includes('getRouterRtpCapabilities') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Checking Server Capabilities</div>
|
||||
<small>getRouterRtpCapabilities</small>
|
||||
</div>
|
||||
<div className={`${states.includes('routerCapabilities') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Has Server Capabilities</div>
|
||||
<small>routerCapabilities</small>
|
||||
</div>
|
||||
<div className={`${states.includes('createProducerTransport') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Create Producers Transport (Waiting for user to start stream)</div>
|
||||
<small>createProducerTransport</small>
|
||||
</div>
|
||||
<div className={`${states.includes('producerTransportCreated') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Created Producers Transport</div>
|
||||
<small>producerTransportCreated</small>
|
||||
</div>
|
||||
<div className={`${states.includes('producerTransportCreated') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Created Producers Transport</div>
|
||||
<small>producerTransportCreated</small>
|
||||
</div>
|
||||
<div className={`${states.includes('createSendTransport') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Created Send Transport</div>
|
||||
<small>createSendTransport</small>
|
||||
</div>
|
||||
<div className={`${states.includes('producerTransportConnected') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Producer Transport Connected</div>
|
||||
<small>producerTransportConnected</small>
|
||||
</div>
|
||||
<div className={`${states.includes('producerTransportConnected') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Send Produced</div>
|
||||
<small>produce</small>
|
||||
</div>
|
||||
<div className={`${states.includes('newProducer') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Found new Produce contents and send to all clients</div>
|
||||
<small>newProducer</small>
|
||||
</div>
|
||||
<div className={`${states.includes('subTransportCreated') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Cunsumber connected</div>
|
||||
<small>subTransportCreated</small>
|
||||
</div>
|
||||
<div className={`${states.includes('connectConsumerTransport') ? 'text-green-400' : 'text-gray-400'} border`}>
|
||||
<div>Accept Consumer Connection</div>
|
||||
<small>connectConsumerTransport</small>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
socket.current = new WebSocket(wsURL)
|
||||
socket.current.onopen = () => {
|
||||
// 1. Create websocket connection
|
||||
setStates(s => [...s, 'ws-connected'])
|
||||
const msg = {
|
||||
type: "getRouterRtpCapabilities"
|
||||
}
|
||||
try {
|
||||
// 2. Get Server capabilities
|
||||
setStates(s => [...s, 'getRouterRtpCapabilities'])
|
||||
socket.current?.send(JSON.stringify(msg))
|
||||
} catch (e) {
|
||||
console.log(`Failed to send capabilities`, e)
|
||||
}
|
||||
}
|
||||
socket.current.onmessage = e => parseWSMessage((e as any).data)
|
||||
}}
|
||||
disabled={connected}
|
||||
>{connected ? 'Connected' : 'Connect'}</button>
|
||||
<button onClick={() => stream()}>
|
||||
Stream
|
||||
</button>
|
||||
<video ref={videoRef}></video>
|
||||
<button
|
||||
onClick={() => {
|
||||
joinStream()
|
||||
}}
|
||||
|
||||
>Join</button>
|
||||
<video ref={videoBRef}></video>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import { CameraIcon, HdIcon, MicrochipIcon, Server, VideoIcon } from 'lucide-react';
|
||||
import Peer, { DataConnection } from 'peerjs';
|
||||
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
type FlowStateType = {
|
||||
color: 'red' | 'blue' | 'green' | 'orange',
|
||||
flow: 'MIC' | 'VIDEO' | 'SERVER' | 'NO_PEERS' | 'ROOM',
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export default function WebRTCChatUsingPeer() {
|
||||
let mediaStream
|
||||
// UI
|
||||
|
||||
|
||||
const peerRef = useRef<Peer>(undefined)
|
||||
const [peers, setPeers] = useState<string[]>([])
|
||||
|
||||
|
||||
const connection = useRef<DataConnection>(undefined)
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
// const [mediaStream, setMediaStream] = useState<MediaStream>(null)
|
||||
const [received, setReceivved] = useState<string>('')
|
||||
const [peerId, setPeerId] = useState<string>('')
|
||||
const [theirPeerId, setTheirPeerId] = useState<string>('')
|
||||
const [myStream, setMyStream] = useState<MediaStream>(null);
|
||||
|
||||
const [devices, setDevices] = useState<MediaDeviceInfo[]>([])
|
||||
const [selectedDevice, setSelectedDevice] = useState<{ video: { deviceId: { exact: string } }, audio: { deviceId: { exact: string } } }>()
|
||||
|
||||
const peer = new Peer({
|
||||
host: "localhost",
|
||||
port: 4000,
|
||||
path: "/myapp",
|
||||
secure: false, // Use true with HTTPS
|
||||
});
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
peer.on("open", function (id) {
|
||||
setPeerId(id)
|
||||
console.log("My peer ID is: " + id);
|
||||
});
|
||||
}, [])
|
||||
|
||||
|
||||
peer.on('connection', function (conn) {
|
||||
console.log(`Got Connection`, conn)
|
||||
conn.on("data", function (data) {
|
||||
setReceivved(data as string)
|
||||
console.log("Received", data);
|
||||
});
|
||||
conn.send("Hello!");
|
||||
});
|
||||
// useEffect(() => {
|
||||
// // Get user media
|
||||
// navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// .then(stream => {
|
||||
// // setMyStream(stream);
|
||||
// // if (videoRef.current) {
|
||||
// // videoRef.current.srcObject = stream;
|
||||
// // }
|
||||
// })
|
||||
// .catch(err => console.error("Failed to get local stream", err));
|
||||
|
||||
// // Initialize PeerJS
|
||||
// // const peer = new Peer(); // PeerJS manages server connection
|
||||
// // currentPeer.current = peer;
|
||||
|
||||
// peer.on('open', (id) => {
|
||||
// setPeerId(id);
|
||||
// console.log('My peer ID is:', id);
|
||||
// });
|
||||
|
||||
// // Listen for incoming calls
|
||||
// peer.on('call', (call) => {
|
||||
// if (myStream) {
|
||||
// // Answer the call with your stream
|
||||
// call.answer(myStream);
|
||||
// call.on('stream', (remoteStream) => {
|
||||
// // Show stream in remote video element
|
||||
// if (videoRef.current) {
|
||||
// videoRef.current.srcObject = remoteStream;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// peer.on('error', (err) => console.error(err));
|
||||
|
||||
// return () => {
|
||||
// if (peer) {
|
||||
// peer.destroy();
|
||||
// }
|
||||
// };
|
||||
// }, [myStream]);
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// const load = async () => {
|
||||
// // Load media devices
|
||||
// const res = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// // .then(async res =>
|
||||
// setDevices(await navigator.mediaDevices.enumerateDevices())
|
||||
// // )
|
||||
// // setFlowIdx(1)
|
||||
// // setFlowIdx(2)
|
||||
|
||||
// // console.log(await navigator.mediaDevices.enumerateDevices())
|
||||
// // console.log(await navigator.mediaDevices.getUserMedia().)
|
||||
// }
|
||||
// load()
|
||||
// peer.on("call", function (call) {
|
||||
// alert(`You have a call`)
|
||||
// // Answer the call, providing our mediaStream
|
||||
// // if (mediaStream) {
|
||||
// call.answer(mediaStream);
|
||||
|
||||
// call.on('stream', (remoteStream) => {
|
||||
// // Show stream in remote video element
|
||||
// if (videoRef.current) {
|
||||
// videoRef.current.srcObject = remoteStream;
|
||||
// }
|
||||
// });
|
||||
// // }
|
||||
// });
|
||||
// }, [])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{JSON.stringify(peerId)}
|
||||
<div>
|
||||
<b>Settings</b>
|
||||
<b>Video</b>
|
||||
{devices.filter(a => a.kind === 'videoinput').map(device =>
|
||||
<div key={device.deviceId}
|
||||
onClick={() => setSelectedDevice((d: any) => {
|
||||
return { ...d, video: { deviceId: { exact: device.deviceId } } }
|
||||
})}
|
||||
className={`${selectedDevice && selectedDevice.video && device.deviceId === selectedDevice?.video.deviceId.exact ? 'bg-green-300' : ''}`}
|
||||
>
|
||||
{device.label}
|
||||
</div>
|
||||
)}
|
||||
<b>Audio</b>
|
||||
{devices.filter(a => a.kind === 'audioinput').map(device =>
|
||||
<div key={device.deviceId}
|
||||
onClick={() => setSelectedDevice((d: any) => {
|
||||
return { ...d, audio: { deviceId: { exact: device.deviceId } } }
|
||||
})}
|
||||
className={`${selectedDevice && selectedDevice.audio && device.deviceId === selectedDevice?.audio.deviceId.exact ? 'bg-green-300' : ''}`}
|
||||
|
||||
>
|
||||
{device.label}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<input placeholder='Cannect to' onChange={e => setTheirPeerId(e.target.value)} />
|
||||
<button onClick={() => {
|
||||
console.log(`Connecting`)
|
||||
connection.current
|
||||
= peer.connect(theirPeerId, {
|
||||
|
||||
});
|
||||
}}>Start</button>
|
||||
<button
|
||||
className='p-2'
|
||||
onClick={() => {
|
||||
// const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
||||
|
||||
navigator.mediaDevices.getUserMedia(selectedDevice)
|
||||
.then(stream => peer.call(theirPeerId, stream))
|
||||
// const call = peer.call(theirPeerId, stream);
|
||||
|
||||
// const stream = await navigator.mediaDevices.getUserMedia({
|
||||
// video: { deviceId: { exact: selectedVideoId } }
|
||||
// });
|
||||
}}
|
||||
>Call</button>
|
||||
<textarea onChange={e => connection.current?.send(e.target.value)} placeholder='Message' rows={6} />
|
||||
{JSON.stringify(received)}
|
||||
<video ref={videoRef} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import { CameraIcon, HdIcon, MicrochipIcon, Server, VideoIcon } from 'lucide-react';
|
||||
import Peer, { DataConnection } from 'peerjs';
|
||||
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
type FlowStateType = {
|
||||
color: 'red' | 'blue' | 'green' | 'orange',
|
||||
flow: 'MIC' | 'VIDEO' | 'SERVER' | 'NO_PEERS' | 'ROOM',
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export default function WebRTCChatUsingPeer() {
|
||||
let mediaStream
|
||||
// UI
|
||||
|
||||
// const [flowSection, setFlowSection] = useState<'MIC' | 'VIDEO' | 'SERVER'>('MIC')
|
||||
const [flowState, setFlowState] = useState<FlowStateType>({
|
||||
color: 'blue',
|
||||
flow: 'SERVER',
|
||||
loading: false
|
||||
})
|
||||
|
||||
|
||||
// const [flow, setFlow] = useState<{
|
||||
// screen: ReactNode,
|
||||
// loading: boolean,
|
||||
// color: 'red' | 'blue' | 'green' | 'orange',
|
||||
// options?: any
|
||||
// }[]>([
|
||||
// // {
|
||||
// // loading: false,
|
||||
// // screen: <SelectMicrophone setLoading={setLoading} />,
|
||||
// // color: 'orange',
|
||||
// // options: [{
|
||||
// // label: 'Next',
|
||||
// // onSelect: (a: any) => { }
|
||||
// // }]
|
||||
// // },
|
||||
// {
|
||||
// loading: false,
|
||||
// screen: <>
|
||||
// <CameraIcon size={45} />
|
||||
// Select video device
|
||||
// <select>
|
||||
// <option>Select</option>
|
||||
// </select>
|
||||
// </>,
|
||||
// color: 'orange'
|
||||
// }, {
|
||||
// loading: true,
|
||||
// screen: <ConnectingToSerever />,
|
||||
// color: 'blue'
|
||||
// }])
|
||||
|
||||
|
||||
const peerRef = useRef<Peer>(undefined)
|
||||
const [peers, setPeers] = useState<string[]>([])
|
||||
|
||||
|
||||
const connection = useRef<DataConnection>(undefined)
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
// const [mediaStream, setMediaStream] = useState<MediaStream>(null)
|
||||
const [received, setReceivved] = useState<string>('')
|
||||
const [peerId, setPeerId] = useState<string>('')
|
||||
const [theirPeerId, setTheirPeerId] = useState<string>('')
|
||||
const [myStream, setMyStream] = useState<MediaStream>(null);
|
||||
|
||||
const [devices, setDevices] = useState<MediaDeviceInfo[]>([])
|
||||
const [selectedDevice, setSelectedDevice] = useState<{ video: { deviceId: { exact: string } }, audio: { deviceId: { exact: string } } }>()
|
||||
|
||||
const handleFindPeers = () => {
|
||||
peerRef.current?.listAllPeers(peers => {
|
||||
setPeers(() => peers)
|
||||
console.log(`PEERS`, peers)
|
||||
if (peers.length > 1) {
|
||||
console.log(`CONNECT TO: `, peers.filter(a => a !== peerId)[0])
|
||||
if (!connection.current)
|
||||
peerRef.current?.connect(peers.filter(a => a !== peerId)[0])
|
||||
setFlowState(s => { return { ...s, flow: 'ROOM', color: 'blue', loading: true } })
|
||||
} else {
|
||||
setFlowState(s => { return { ...s, flow: 'NO_PEERS', color: 'blue', loading: true } })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleConnectServer = () => {
|
||||
setFlowState(s => { return { ...s, flow: 'SERVER' } })
|
||||
|
||||
peerRef.current = new Peer({
|
||||
host: "localhost",
|
||||
port: 4000,
|
||||
path: "/myapp",
|
||||
secure: false, // Use true with HTTPS
|
||||
})
|
||||
|
||||
// peerRef.current.socket.on('message')
|
||||
peerRef.current.on("open", function (id) {
|
||||
setPeerId(id)
|
||||
setFlowState(s => { return { ...s, color: 'green', loading: false } })
|
||||
console.log("My peer ID is: " + id, 'Connected Peers:',);
|
||||
handleFindPeers()
|
||||
});
|
||||
|
||||
peerRef.current.on('error', function (err) {
|
||||
setFlowState(s => { return { ...s, color: 'red', loading: false } })
|
||||
})
|
||||
|
||||
peerRef.current.on('connection', function (conn) {
|
||||
console.log(`Got Connection`, conn)
|
||||
connection.current = conn
|
||||
// handleFindPeers()
|
||||
setFlowState(s => { return { ...s, flow: 'ROOM' } })
|
||||
connection.current.on("data", function (data) {
|
||||
// setReceivved(data as string)
|
||||
console.log("Received", data);
|
||||
});
|
||||
connection.current.send("Hello!");
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleConnectServer()
|
||||
}, [])
|
||||
|
||||
|
||||
// const peer = new Peer({
|
||||
// host: "localhost",
|
||||
// port: 4000,
|
||||
// path: "/myapp",
|
||||
// secure: false, // Use true with HTTPS
|
||||
// });
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// peer.on("open", function (id) {
|
||||
// setPeerId(id)
|
||||
// console.log("My peer ID is: " + id);
|
||||
// });
|
||||
// }, [])
|
||||
|
||||
|
||||
// peer.on('connection', function (conn) {
|
||||
// console.log(`Got Connection`, conn)
|
||||
// conn.on("data", function (data) {
|
||||
// setReceivved(data as string)
|
||||
// console.log("Received", data);
|
||||
// });
|
||||
// conn.send("Hello!");
|
||||
// });
|
||||
// useEffect(() => {
|
||||
// // Get user media
|
||||
// navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// .then(stream => {
|
||||
// // setMyStream(stream);
|
||||
// // if (videoRef.current) {
|
||||
// // videoRef.current.srcObject = stream;
|
||||
// // }
|
||||
// })
|
||||
// .catch(err => console.error("Failed to get local stream", err));
|
||||
|
||||
// // Initialize PeerJS
|
||||
// // const peer = new Peer(); // PeerJS manages server connection
|
||||
// // currentPeer.current = peer;
|
||||
|
||||
// peer.on('open', (id) => {
|
||||
// setPeerId(id);
|
||||
// console.log('My peer ID is:', id);
|
||||
// });
|
||||
|
||||
// // Listen for incoming calls
|
||||
// peer.on('call', (call) => {
|
||||
// if (myStream) {
|
||||
// // Answer the call with your stream
|
||||
// call.answer(myStream);
|
||||
// call.on('stream', (remoteStream) => {
|
||||
// // Show stream in remote video element
|
||||
// if (videoRef.current) {
|
||||
// videoRef.current.srcObject = remoteStream;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// peer.on('error', (err) => console.error(err));
|
||||
|
||||
// return () => {
|
||||
// if (peer) {
|
||||
// peer.destroy();
|
||||
// }
|
||||
// };
|
||||
// }, [myStream]);
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// const load = async () => {
|
||||
// // Load media devices
|
||||
// const res = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// // .then(async res =>
|
||||
// setDevices(await navigator.mediaDevices.enumerateDevices())
|
||||
// // )
|
||||
// // setFlowIdx(1)
|
||||
// // setFlowIdx(2)
|
||||
|
||||
// // console.log(await navigator.mediaDevices.enumerateDevices())
|
||||
// // console.log(await navigator.mediaDevices.getUserMedia().)
|
||||
// }
|
||||
// load()
|
||||
// peer.on("call", function (call) {
|
||||
// alert(`You have a call`)
|
||||
// // Answer the call, providing our mediaStream
|
||||
// // if (mediaStream) {
|
||||
// call.answer(mediaStream);
|
||||
|
||||
// call.on('stream', (remoteStream) => {
|
||||
// // Show stream in remote video element
|
||||
// if (videoRef.current) {
|
||||
// videoRef.current.srcObject = remoteStream;
|
||||
// }
|
||||
// });
|
||||
// // }
|
||||
// });
|
||||
// }, [])
|
||||
|
||||
if (flowState.flow === 'ROOM') {
|
||||
return <div className='flex w-screen h-screen'>
|
||||
<div className='grow'></div>
|
||||
<form onSubmit={(form) => {
|
||||
form.preventDefault()
|
||||
const formData = new FormData(form.target);
|
||||
// const det = Object.fromEntries(formData.entries())
|
||||
try {
|
||||
console.log(`Sending:`, connection.current, formData.get('message') as string)
|
||||
connection.current?.send(formData.get('message') as string)
|
||||
} catch (e) {
|
||||
console.error('Failed to send message', e)
|
||||
}
|
||||
}}>
|
||||
<input name='message' placeholder='message' />
|
||||
<button type='submit'>Send</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
return (<Screen
|
||||
color={flowState.color}
|
||||
loading={flowState.loading}>
|
||||
{
|
||||
{
|
||||
'MIC': <SelectMicrophone setFlowState={setFlowState}
|
||||
onChange={e => {
|
||||
setSelectedDevice((d: any) => {
|
||||
return { ...d, audio: { deviceId: { exact: e } } }
|
||||
})
|
||||
setFlowState(s => { return { ...s, flow: 'VIDEO' } })
|
||||
// setFlowSection('VIDEO')
|
||||
}}
|
||||
/>,
|
||||
'VIDEO': <SelectVideo setFlowState={setFlowState}
|
||||
onChange={e => {
|
||||
setSelectedDevice((d: any) => {
|
||||
return { ...d, video: { deviceId: { exact: e } } }
|
||||
})
|
||||
handleConnectServer()
|
||||
}}
|
||||
/>,
|
||||
'SERVER': <ConnectingToSerever
|
||||
setFlowState={setFlowState}
|
||||
/>,
|
||||
'NO_PEERS': <>Waiting for others to join</>,
|
||||
'ROOM': <></>
|
||||
}[flowState.flow]
|
||||
}
|
||||
{/* {flow[flowIdx].screen} */}
|
||||
</Screen>)
|
||||
// return (
|
||||
// <div>
|
||||
// {JSON.stringify(peerId)}
|
||||
// <div>
|
||||
// <b>Settings</b>
|
||||
// <b>Video</b>
|
||||
// {devices.filter(a => a.kind === 'videoinput').map(device =>
|
||||
// <div key={device.deviceId}
|
||||
// onClick={() => setSelectedDevice((d: any) => {
|
||||
// return { ...d, video: { deviceId: { exact: device.deviceId } } }
|
||||
// })}
|
||||
// className={`${selectedDevice && selectedDevice.video && device.deviceId === selectedDevice?.video.deviceId.exact ? 'bg-green-300' : ''}`}
|
||||
// >
|
||||
// {device.label}
|
||||
// </div>
|
||||
// )}
|
||||
// <b>Audio</b>
|
||||
// {devices.filter(a => a.kind === 'audioinput').map(device =>
|
||||
// <div key={device.deviceId}
|
||||
// onClick={() => setSelectedDevice((d: any) => {
|
||||
// return { ...d, audio: { deviceId: { exact: device.deviceId } } }
|
||||
// })}
|
||||
// className={`${selectedDevice && selectedDevice.audio && device.deviceId === selectedDevice?.audio.deviceId.exact ? 'bg-green-300' : ''}`}
|
||||
|
||||
// >
|
||||
// {device.label}
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// <input placeholder='Cannect to' onChange={e => setTheirPeerId(e.target.value)} />
|
||||
// <button onClick={() => {
|
||||
// console.log(`Connecting`)
|
||||
// connection.current
|
||||
// = peer.connect(theirPeerId, {
|
||||
|
||||
// });
|
||||
// }}>Start</button>
|
||||
// <button
|
||||
// className='p-2'
|
||||
// onClick={() => {
|
||||
// // const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
||||
|
||||
// navigator.mediaDevices.getUserMedia(selectedDevice)
|
||||
// .then(stream => peer.call(theirPeerId, stream))
|
||||
// // const call = peer.call(theirPeerId, stream);
|
||||
|
||||
// // const stream = await navigator.mediaDevices.getUserMedia({
|
||||
// // video: { deviceId: { exact: selectedVideoId } }
|
||||
// // });
|
||||
// }}
|
||||
// >Call</button>
|
||||
// <textarea onChange={e => connection.current?.send(e.target.value)} placeholder='Message' rows={6} />
|
||||
// {JSON.stringify(received)}
|
||||
// <video ref={videoRef} />
|
||||
// </div>
|
||||
// )
|
||||
}
|
||||
|
||||
|
||||
export function Screen({ color, loading, children }: {
|
||||
color: 'red' | 'blue' | 'green' | 'orange',
|
||||
loading: boolean
|
||||
children: ReactNode
|
||||
}) {
|
||||
return <div
|
||||
style={{
|
||||
background: color == 'orange' ? '#441306' : color == 'green' ? '#032e15' : color == 'red' ? '#460809' : '#162456'
|
||||
}}
|
||||
className={`h-screen w-screen flex items-center align-middle justify-center`}>
|
||||
<>
|
||||
<div className='relative '>
|
||||
{/* <div className='absolute flex items-center align-middle justify-center -left-20 -top-10 bg-blue-900 hover:bg-blue-800 shadow shadow-white shadow-2xl rounded-full grow w-64 h-64'>
|
||||
ITEM 1
|
||||
</div>
|
||||
<div className='absolute right-0 bg-blue-950 hover:bg-blue-900 w-64 h-64'>
|
||||
ITEM 2
|
||||
</div>
|
||||
<div className='absolute bottom-0 bg-blue-950 hover:bg-blue-900'>
|
||||
ITEM 3
|
||||
</div> */}
|
||||
<div
|
||||
style={{
|
||||
background: color == 'orange' ?
|
||||
'radial-gradient(circle, oklch(40.8% 0.123 38.172) 0%, rgba(0,0,0,0.3) 100%)'
|
||||
: color == 'green' ?
|
||||
'radial-gradient(circle, oklch(39.3% 0.095 152.535) 0%, rgba(0,0,0,0.3) 100%)' :
|
||||
color == 'red' ?
|
||||
'radial-gradient(circle,oklch(39.6% 0.141 25.723) 0%, rgba(0,0,0,0.3) 100%)'
|
||||
:
|
||||
'radial-gradient(circle,oklch(37.9% 0.146 265.522) 0%, rgba(0,0,0,0.3) 100%)'
|
||||
|
||||
}}
|
||||
className={`w-96 h-95 rounded-full relative border-8 shadow-lg ${loading && `animate-spin shadow-purple-300/75`} text-3xl text-white flex items-center justify-center align-middle `}>
|
||||
<div className="absolute inset-0 flex items-center justify-center animate-[inherit] direction-reverse">
|
||||
<span className="flex gap-2 flex-col items-center font-bold text-white text-shadow-white text-shadow-2xs">
|
||||
{children}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
|
||||
export function ConnectingToSerever({
|
||||
setFlowState,
|
||||
}: {
|
||||
setFlowState: React.Dispatch<React.SetStateAction<FlowStateType>>
|
||||
}) {
|
||||
useEffect(() => { setFlowState(s => { return { ...s, color: 'blue', loading: true } }) }, [])
|
||||
|
||||
|
||||
// const peerConn = useCallback(() => {
|
||||
|
||||
// const peer = new Peer({
|
||||
// host: "localhost",
|
||||
// port: 4000,
|
||||
// path: "/myapp",
|
||||
// secure: false, // Use true with HTTPS
|
||||
// });
|
||||
// peer.on("open", function (id) {
|
||||
// setPeerId(id)
|
||||
// setColor('green')
|
||||
// console.log("My peer ID is: " + id);
|
||||
// });
|
||||
|
||||
// peer.on('error', function (err) {
|
||||
// setColor('red')
|
||||
// setLoading(false)
|
||||
// })
|
||||
|
||||
// peer.on('connection', function (conn) {
|
||||
// console.log(`Got Connection`, conn)
|
||||
// conn.on("data", function (data) {
|
||||
// // setReceivved(data as string)
|
||||
// console.log("Received", data);
|
||||
// });
|
||||
// conn.send("Hello!");
|
||||
// });
|
||||
// }, [])
|
||||
|
||||
// useEffect(() => {
|
||||
// const load = async () => {
|
||||
// peer.on("open", function (id) {
|
||||
// setPeerId(id)
|
||||
// setColor('green')
|
||||
// console.log("My peer ID is: " + id);
|
||||
// });
|
||||
|
||||
// peer.on('error', function (err) {
|
||||
// setColor('red')
|
||||
// setLoading(false)
|
||||
// })
|
||||
|
||||
// peer.on('connection', function (conn) {
|
||||
// console.log(`Got Connection`, conn)
|
||||
// conn.on("data", function (data) {
|
||||
// // setReceivved(data as string)
|
||||
// console.log("Received", data);
|
||||
// });
|
||||
// conn.send("Hello!");
|
||||
// });
|
||||
|
||||
// }
|
||||
// setColor('green')
|
||||
// setLoading(true)
|
||||
// load()
|
||||
// }, [])
|
||||
return <>
|
||||
<Server size={45} />
|
||||
Connecting to Server
|
||||
</>
|
||||
}
|
||||
|
||||
export function SelectMicrophone({
|
||||
setFlowState,
|
||||
onChange
|
||||
}: {
|
||||
setFlowState: React.Dispatch<React.SetStateAction<FlowStateType>>
|
||||
onChange: (device: string) => void
|
||||
}) {
|
||||
const [devices, setDevices] = useState<MediaDeviceInfo[]>([])
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
// Load media devices
|
||||
// setTimeout(() => setLoading(false), 3000)
|
||||
// setLoading(false)
|
||||
const res = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// // .then(async res =>
|
||||
setDevices(await navigator.mediaDevices.enumerateDevices())
|
||||
|
||||
setFlowState(s => { return { ...s, color: 'orange', loading: false } })
|
||||
}
|
||||
setFlowState(s => { return { ...s, color: 'green', loading: true } })
|
||||
load()
|
||||
}, [])
|
||||
return <>
|
||||
<MicrochipIcon size={45} />
|
||||
{
|
||||
devices.length == 0 ? 'Finding Audio Devices' : <>
|
||||
|
||||
Select audio device
|
||||
<select className='text-sm w-64 bg-white p-2 text-black'
|
||||
onChange={e => onChange(e.target.value)}
|
||||
>
|
||||
<option>Select</option>
|
||||
{devices.filter(a => a.kind === 'audioinput').map((device, i) =>
|
||||
<option key={`device-${i}`} value={device.deviceId}>{device.label}</option>
|
||||
)}
|
||||
|
||||
</select>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
export function SelectVideo({
|
||||
setFlowState,
|
||||
onChange
|
||||
}: {
|
||||
setFlowState: React.Dispatch<React.SetStateAction<FlowStateType>>
|
||||
onChange: (device: string) => void
|
||||
}) {
|
||||
const [devices, setDevices] = useState<MediaDeviceInfo[]>([])
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
// Load media devices
|
||||
// setTimeout(() => setLoading(false), 3000)
|
||||
// setLoading(false)
|
||||
const res = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// // .then(async res =>
|
||||
setDevices(await navigator.mediaDevices.enumerateDevices())
|
||||
setFlowState(s => { return { ...s, color: 'orange', loading: false } })
|
||||
}
|
||||
setFlowState(s => { return { ...s, color: 'green', loading: true } })
|
||||
load()
|
||||
}, [])
|
||||
return <>
|
||||
<VideoIcon size={45} />
|
||||
{
|
||||
devices.length == 0 ? 'Finding Video Devices' : <>
|
||||
|
||||
Select video device
|
||||
<select className='text-sm w-64 bg-white p-2 text-black'
|
||||
onChange={e => onChange(e.target.value)}
|
||||
>
|
||||
<option>Select</option>
|
||||
{devices.filter(a => a.kind === 'videoinput').map((device, i) =>
|
||||
<option key={`device-${i}`} value={device.deviceId}>{device.label}</option>
|
||||
)}
|
||||
|
||||
</select>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
@@ -0,0 +1,506 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import { CameraIcon, HdIcon, MicrochipIcon, Server, VideoIcon } from 'lucide-react';
|
||||
import Peer, { DataConnection } from 'peerjs';
|
||||
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
type FlowStateType = {
|
||||
color: 'red' | 'blue' | 'green' | 'orange',
|
||||
flow: 'MIC' | 'VIDEO' | 'SERVER' | 'NO_PEERS' | 'ROOM',
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export default function WebRTCChatUsingPeer() {
|
||||
let mediaStream
|
||||
// UI
|
||||
|
||||
// const [flowSection, setFlowSection] = useState<'MIC' | 'VIDEO' | 'SERVER'>('MIC')
|
||||
const [flowState, setFlowState] = useState<FlowStateType>({
|
||||
color: 'blue',
|
||||
flow: 'SERVER',
|
||||
loading: false
|
||||
})
|
||||
|
||||
|
||||
// const [flow, setFlow] = useState<{
|
||||
// screen: ReactNode,
|
||||
// loading: boolean,
|
||||
// color: 'red' | 'blue' | 'green' | 'orange',
|
||||
// options?: any
|
||||
// }[]>([
|
||||
// // {
|
||||
// // loading: false,
|
||||
// // screen: <SelectMicrophone setLoading={setLoading} />,
|
||||
// // color: 'orange',
|
||||
// // options: [{
|
||||
// // label: 'Next',
|
||||
// // onSelect: (a: any) => { }
|
||||
// // }]
|
||||
// // },
|
||||
// {
|
||||
// loading: false,
|
||||
// screen: <>
|
||||
// <CameraIcon size={45} />
|
||||
// Select video device
|
||||
// <select>
|
||||
// <option>Select</option>
|
||||
// </select>
|
||||
// </>,
|
||||
// color: 'orange'
|
||||
// }, {
|
||||
// loading: true,
|
||||
// screen: <ConnectingToSerever />,
|
||||
// color: 'blue'
|
||||
// }])
|
||||
|
||||
|
||||
const peerRef = useRef<Peer>(undefined)
|
||||
const [peers, setPeers] = useState<string[]>([])
|
||||
|
||||
|
||||
const connection = useRef<DataConnection>(undefined)
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
// const [mediaStream, setMediaStream] = useState<MediaStream>(null)
|
||||
const [received, setReceivved] = useState<string>('')
|
||||
const [peerId, setPeerId] = useState<string>('')
|
||||
const [theirPeerId, setTheirPeerId] = useState<string>('')
|
||||
const [myStream, setMyStream] = useState<MediaStream>(null);
|
||||
|
||||
const [devices, setDevices] = useState<MediaDeviceInfo[]>([])
|
||||
const [selectedDevice, setSelectedDevice] = useState<{ video: { deviceId: { exact: string } }, audio: { deviceId: { exact: string } } }>()
|
||||
|
||||
const handleFindPeers = () => {
|
||||
peerRef.current?.listAllPeers(peers => {
|
||||
setPeers(() => peers)
|
||||
console.log(`PEERS`, peers)
|
||||
if (peers.length > 1) {
|
||||
peerRef.current?.connect(peers[1])
|
||||
setFlowState(s => { return { ...s, flow: 'ROOM', color: 'blue', loading: true } })
|
||||
} else {
|
||||
setFlowState(s => { return { ...s, flow: 'NO_PEERS', color: 'blue', loading: true } })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleConnectServer = () => {
|
||||
setFlowState(s => { return { ...s, flow: 'SERVER' } })
|
||||
|
||||
peerRef.current = new Peer({
|
||||
host: "localhost",
|
||||
port: 4000,
|
||||
path: "/myapp",
|
||||
secure: false, // Use true with HTTPS
|
||||
})
|
||||
|
||||
// peerRef.current.socket.on('message')
|
||||
peerRef.current.on("open", function (id) {
|
||||
setPeerId(id)
|
||||
setFlowState(s => { return { ...s, color: 'green', loading: false } })
|
||||
console.log("My peer ID is: " + id, 'Connected Peers:',);
|
||||
handleFindPeers()
|
||||
});
|
||||
|
||||
peerRef.current.on('error', function (err) {
|
||||
setFlowState(s => { return { ...s, color: 'red', loading: false } })
|
||||
})
|
||||
|
||||
peerRef.current.on('connection', function (conn) {
|
||||
console.log(`Got Connection`, conn)
|
||||
conn.on("data", function (data) {
|
||||
// setReceivved(data as string)
|
||||
console.log("Received", data);
|
||||
});
|
||||
conn.send("Hello!");
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleConnectServer()
|
||||
}, [])
|
||||
|
||||
|
||||
// const peer = new Peer({
|
||||
// host: "localhost",
|
||||
// port: 4000,
|
||||
// path: "/myapp",
|
||||
// secure: false, // Use true with HTTPS
|
||||
// });
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// peer.on("open", function (id) {
|
||||
// setPeerId(id)
|
||||
// console.log("My peer ID is: " + id);
|
||||
// });
|
||||
// }, [])
|
||||
|
||||
|
||||
// peer.on('connection', function (conn) {
|
||||
// console.log(`Got Connection`, conn)
|
||||
// conn.on("data", function (data) {
|
||||
// setReceivved(data as string)
|
||||
// console.log("Received", data);
|
||||
// });
|
||||
// conn.send("Hello!");
|
||||
// });
|
||||
// useEffect(() => {
|
||||
// // Get user media
|
||||
// navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// .then(stream => {
|
||||
// // setMyStream(stream);
|
||||
// // if (videoRef.current) {
|
||||
// // videoRef.current.srcObject = stream;
|
||||
// // }
|
||||
// })
|
||||
// .catch(err => console.error("Failed to get local stream", err));
|
||||
|
||||
// // Initialize PeerJS
|
||||
// // const peer = new Peer(); // PeerJS manages server connection
|
||||
// // currentPeer.current = peer;
|
||||
|
||||
// peer.on('open', (id) => {
|
||||
// setPeerId(id);
|
||||
// console.log('My peer ID is:', id);
|
||||
// });
|
||||
|
||||
// // Listen for incoming calls
|
||||
// peer.on('call', (call) => {
|
||||
// if (myStream) {
|
||||
// // Answer the call with your stream
|
||||
// call.answer(myStream);
|
||||
// call.on('stream', (remoteStream) => {
|
||||
// // Show stream in remote video element
|
||||
// if (videoRef.current) {
|
||||
// videoRef.current.srcObject = remoteStream;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// peer.on('error', (err) => console.error(err));
|
||||
|
||||
// return () => {
|
||||
// if (peer) {
|
||||
// peer.destroy();
|
||||
// }
|
||||
// };
|
||||
// }, [myStream]);
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// const load = async () => {
|
||||
// // Load media devices
|
||||
// const res = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// // .then(async res =>
|
||||
// setDevices(await navigator.mediaDevices.enumerateDevices())
|
||||
// // )
|
||||
// // setFlowIdx(1)
|
||||
// // setFlowIdx(2)
|
||||
|
||||
// // console.log(await navigator.mediaDevices.enumerateDevices())
|
||||
// // console.log(await navigator.mediaDevices.getUserMedia().)
|
||||
// }
|
||||
// load()
|
||||
// peer.on("call", function (call) {
|
||||
// alert(`You have a call`)
|
||||
// // Answer the call, providing our mediaStream
|
||||
// // if (mediaStream) {
|
||||
// call.answer(mediaStream);
|
||||
|
||||
// call.on('stream', (remoteStream) => {
|
||||
// // Show stream in remote video element
|
||||
// if (videoRef.current) {
|
||||
// videoRef.current.srcObject = remoteStream;
|
||||
// }
|
||||
// });
|
||||
// // }
|
||||
// });
|
||||
// }, [])
|
||||
|
||||
return (<Screen
|
||||
color={flowState.color}
|
||||
loading={flowState.loading}>
|
||||
{
|
||||
{
|
||||
'MIC': <SelectMicrophone setFlowState={setFlowState}
|
||||
onChange={e => {
|
||||
setSelectedDevice((d: any) => {
|
||||
return { ...d, audio: { deviceId: { exact: e } } }
|
||||
})
|
||||
setFlowState(s => { return { ...s, flow: 'VIDEO' } })
|
||||
// setFlowSection('VIDEO')
|
||||
}}
|
||||
/>,
|
||||
'VIDEO': <SelectVideo setFlowState={setFlowState}
|
||||
onChange={e => {
|
||||
setSelectedDevice((d: any) => {
|
||||
return { ...d, video: { deviceId: { exact: e } } }
|
||||
})
|
||||
handleConnectServer()
|
||||
}}
|
||||
/>,
|
||||
'SERVER': <ConnectingToSerever
|
||||
setFlowState={setFlowState}
|
||||
/>,
|
||||
'NO_PEERS': <>Waiting for others to join</>,
|
||||
'ROOM': <></>
|
||||
}[flowState.flow]
|
||||
}
|
||||
{/* {flow[flowIdx].screen} */}
|
||||
</Screen>)
|
||||
// return (
|
||||
// <div>
|
||||
// {JSON.stringify(peerId)}
|
||||
// <div>
|
||||
// <b>Settings</b>
|
||||
// <b>Video</b>
|
||||
// {devices.filter(a => a.kind === 'videoinput').map(device =>
|
||||
// <div key={device.deviceId}
|
||||
// onClick={() => setSelectedDevice((d: any) => {
|
||||
// return { ...d, video: { deviceId: { exact: device.deviceId } } }
|
||||
// })}
|
||||
// className={`${selectedDevice && selectedDevice.video && device.deviceId === selectedDevice?.video.deviceId.exact ? 'bg-green-300' : ''}`}
|
||||
// >
|
||||
// {device.label}
|
||||
// </div>
|
||||
// )}
|
||||
// <b>Audio</b>
|
||||
// {devices.filter(a => a.kind === 'audioinput').map(device =>
|
||||
// <div key={device.deviceId}
|
||||
// onClick={() => setSelectedDevice((d: any) => {
|
||||
// return { ...d, audio: { deviceId: { exact: device.deviceId } } }
|
||||
// })}
|
||||
// className={`${selectedDevice && selectedDevice.audio && device.deviceId === selectedDevice?.audio.deviceId.exact ? 'bg-green-300' : ''}`}
|
||||
|
||||
// >
|
||||
// {device.label}
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// <input placeholder='Cannect to' onChange={e => setTheirPeerId(e.target.value)} />
|
||||
// <button onClick={() => {
|
||||
// console.log(`Connecting`)
|
||||
// connection.current
|
||||
// = peer.connect(theirPeerId, {
|
||||
|
||||
// });
|
||||
// }}>Start</button>
|
||||
// <button
|
||||
// className='p-2'
|
||||
// onClick={() => {
|
||||
// // const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
||||
|
||||
// navigator.mediaDevices.getUserMedia(selectedDevice)
|
||||
// .then(stream => peer.call(theirPeerId, stream))
|
||||
// // const call = peer.call(theirPeerId, stream);
|
||||
|
||||
// // const stream = await navigator.mediaDevices.getUserMedia({
|
||||
// // video: { deviceId: { exact: selectedVideoId } }
|
||||
// // });
|
||||
// }}
|
||||
// >Call</button>
|
||||
// <textarea onChange={e => connection.current?.send(e.target.value)} placeholder='Message' rows={6} />
|
||||
// {JSON.stringify(received)}
|
||||
// <video ref={videoRef} />
|
||||
// </div>
|
||||
// )
|
||||
}
|
||||
|
||||
|
||||
export function Screen({ color, loading, children }: {
|
||||
color: 'red' | 'blue' | 'green' | 'orange',
|
||||
loading: boolean
|
||||
children: ReactNode
|
||||
}) {
|
||||
return <div
|
||||
style={{
|
||||
background: color == 'orange' ? '#441306' : color == 'green' ? '#032e15' : color == 'red' ? '#460809' : '#162456'
|
||||
}}
|
||||
className={`h-screen w-screen flex items-center align-middle justify-center`}>
|
||||
<>
|
||||
<div className='relative '>
|
||||
{/* <div className='absolute flex items-center align-middle justify-center -left-20 -top-10 bg-blue-900 hover:bg-blue-800 shadow shadow-white shadow-2xl rounded-full grow w-64 h-64'>
|
||||
ITEM 1
|
||||
</div>
|
||||
<div className='absolute right-0 bg-blue-950 hover:bg-blue-900 w-64 h-64'>
|
||||
ITEM 2
|
||||
</div>
|
||||
<div className='absolute bottom-0 bg-blue-950 hover:bg-blue-900'>
|
||||
ITEM 3
|
||||
</div> */}
|
||||
<div
|
||||
style={{
|
||||
background: color == 'orange' ?
|
||||
'radial-gradient(circle, oklch(40.8% 0.123 38.172) 0%, rgba(0,0,0,0.3) 100%)'
|
||||
: color == 'green' ?
|
||||
'radial-gradient(circle, oklch(39.3% 0.095 152.535) 0%, rgba(0,0,0,0.3) 100%)' :
|
||||
color == 'red' ?
|
||||
'radial-gradient(circle,oklch(39.6% 0.141 25.723) 0%, rgba(0,0,0,0.3) 100%)'
|
||||
:
|
||||
'radial-gradient(circle,oklch(37.9% 0.146 265.522) 0%, rgba(0,0,0,0.3) 100%)'
|
||||
|
||||
}}
|
||||
className={`w-96 h-95 rounded-full relative border-8 shadow-lg ${loading && `animate-spin shadow-purple-300/75`} text-3xl text-white flex items-center justify-center align-middle `}>
|
||||
<div className="absolute inset-0 flex items-center justify-center animate-[inherit] direction-reverse">
|
||||
<span className="flex gap-2 flex-col items-center font-bold text-white text-shadow-white text-shadow-2xs">
|
||||
{children}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
|
||||
export function ConnectingToSerever({
|
||||
setFlowState,
|
||||
}: {
|
||||
setFlowState: React.Dispatch<React.SetStateAction<FlowStateType>>
|
||||
}) {
|
||||
useEffect(() => { setFlowState(s => { return { ...s, color: 'blue', loading: true } }) }, [])
|
||||
|
||||
|
||||
// const peerConn = useCallback(() => {
|
||||
|
||||
// const peer = new Peer({
|
||||
// host: "localhost",
|
||||
// port: 4000,
|
||||
// path: "/myapp",
|
||||
// secure: false, // Use true with HTTPS
|
||||
// });
|
||||
// peer.on("open", function (id) {
|
||||
// setPeerId(id)
|
||||
// setColor('green')
|
||||
// console.log("My peer ID is: " + id);
|
||||
// });
|
||||
|
||||
// peer.on('error', function (err) {
|
||||
// setColor('red')
|
||||
// setLoading(false)
|
||||
// })
|
||||
|
||||
// peer.on('connection', function (conn) {
|
||||
// console.log(`Got Connection`, conn)
|
||||
// conn.on("data", function (data) {
|
||||
// // setReceivved(data as string)
|
||||
// console.log("Received", data);
|
||||
// });
|
||||
// conn.send("Hello!");
|
||||
// });
|
||||
// }, [])
|
||||
|
||||
// useEffect(() => {
|
||||
// const load = async () => {
|
||||
// peer.on("open", function (id) {
|
||||
// setPeerId(id)
|
||||
// setColor('green')
|
||||
// console.log("My peer ID is: " + id);
|
||||
// });
|
||||
|
||||
// peer.on('error', function (err) {
|
||||
// setColor('red')
|
||||
// setLoading(false)
|
||||
// })
|
||||
|
||||
// peer.on('connection', function (conn) {
|
||||
// console.log(`Got Connection`, conn)
|
||||
// conn.on("data", function (data) {
|
||||
// // setReceivved(data as string)
|
||||
// console.log("Received", data);
|
||||
// });
|
||||
// conn.send("Hello!");
|
||||
// });
|
||||
|
||||
// }
|
||||
// setColor('green')
|
||||
// setLoading(true)
|
||||
// load()
|
||||
// }, [])
|
||||
return <>
|
||||
<Server size={45} />
|
||||
Connecting to Server
|
||||
</>
|
||||
}
|
||||
|
||||
export function SelectMicrophone({
|
||||
setFlowState,
|
||||
onChange
|
||||
}: {
|
||||
setFlowState: React.Dispatch<React.SetStateAction<FlowStateType>>
|
||||
onChange: (device: string) => void
|
||||
}) {
|
||||
const [devices, setDevices] = useState<MediaDeviceInfo[]>([])
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
// Load media devices
|
||||
// setTimeout(() => setLoading(false), 3000)
|
||||
// setLoading(false)
|
||||
const res = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// // .then(async res =>
|
||||
setDevices(await navigator.mediaDevices.enumerateDevices())
|
||||
|
||||
setFlowState(s => { return { ...s, color: 'orange', loading: false } })
|
||||
}
|
||||
setFlowState(s => { return { ...s, color: 'green', loading: true } })
|
||||
load()
|
||||
}, [])
|
||||
return <>
|
||||
<MicrochipIcon size={45} />
|
||||
{
|
||||
devices.length == 0 ? 'Finding Audio Devices' : <>
|
||||
|
||||
Select audio device
|
||||
<select className='text-sm w-64 bg-white p-2 text-black'
|
||||
onChange={e => onChange(e.target.value)}
|
||||
>
|
||||
<option>Select</option>
|
||||
{devices.filter(a => a.kind === 'audioinput').map((device, i) =>
|
||||
<option key={`device-${i}`} value={device.deviceId}>{device.label}</option>
|
||||
)}
|
||||
|
||||
</select>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
export function SelectVideo({
|
||||
setFlowState,
|
||||
onChange
|
||||
}: {
|
||||
setFlowState: React.Dispatch<React.SetStateAction<FlowStateType>>
|
||||
onChange: (device: string) => void
|
||||
}) {
|
||||
const [devices, setDevices] = useState<MediaDeviceInfo[]>([])
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
// Load media devices
|
||||
// setTimeout(() => setLoading(false), 3000)
|
||||
// setLoading(false)
|
||||
const res = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// // .then(async res =>
|
||||
setDevices(await navigator.mediaDevices.enumerateDevices())
|
||||
setFlowState(s => { return { ...s, color: 'orange', loading: false } })
|
||||
}
|
||||
setFlowState(s => { return { ...s, color: 'green', loading: true } })
|
||||
load()
|
||||
}, [])
|
||||
return <>
|
||||
<VideoIcon size={45} />
|
||||
{
|
||||
devices.length == 0 ? 'Finding Video Devices' : <>
|
||||
|
||||
Select video device
|
||||
<select className='text-sm w-64 bg-white p-2 text-black'
|
||||
onChange={e => onChange(e.target.value)}
|
||||
>
|
||||
<option>Select</option>
|
||||
{devices.filter(a => a.kind === 'videoinput').map((device, i) =>
|
||||
<option key={`device-${i}`} value={device.deviceId}>{device.label}</option>
|
||||
)}
|
||||
|
||||
</select>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import { CameraIcon, HdIcon, MicrochipIcon, Server, VideoIcon } from 'lucide-react';
|
||||
import Peer, { DataConnection } from 'peerjs';
|
||||
import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
type FlowStateType = {
|
||||
color: 'red' | 'blue' | 'green' | 'orange',
|
||||
flow: 'MIC' | 'VIDEO' | 'SERVER' | 'NO_PEERS' | 'ROOM',
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
export default function WebRTCChatUsingPeer() {
|
||||
let mediaStream
|
||||
// UI
|
||||
|
||||
// const [flowSection, setFlowSection] = useState<'MIC' | 'VIDEO' | 'SERVER'>('MIC')
|
||||
const [flowState, setFlowState] = useState<FlowStateType>({
|
||||
color: 'blue',
|
||||
flow: 'SERVER',
|
||||
loading: false
|
||||
})
|
||||
|
||||
|
||||
// const [flow, setFlow] = useState<{
|
||||
// screen: ReactNode,
|
||||
// loading: boolean,
|
||||
// color: 'red' | 'blue' | 'green' | 'orange',
|
||||
// options?: any
|
||||
// }[]>([
|
||||
// // {
|
||||
// // loading: false,
|
||||
// // screen: <SelectMicrophone setLoading={setLoading} />,
|
||||
// // color: 'orange',
|
||||
// // options: [{
|
||||
// // label: 'Next',
|
||||
// // onSelect: (a: any) => { }
|
||||
// // }]
|
||||
// // },
|
||||
// {
|
||||
// loading: false,
|
||||
// screen: <>
|
||||
// <CameraIcon size={45} />
|
||||
// Select video device
|
||||
// <select>
|
||||
// <option>Select</option>
|
||||
// </select>
|
||||
// </>,
|
||||
// color: 'orange'
|
||||
// }, {
|
||||
// loading: true,
|
||||
// screen: <ConnectingToSerever />,
|
||||
// color: 'blue'
|
||||
// }])
|
||||
|
||||
|
||||
const peerRef = useRef<Peer>(undefined)
|
||||
const [peers, setPeers] = useState<string[]>([])
|
||||
|
||||
|
||||
const connection = useRef<DataConnection>(undefined)
|
||||
const videoRef = useRef<HTMLVideoElement>(null)
|
||||
// const [mediaStream, setMediaStream] = useState<MediaStream>(null)
|
||||
const [received, setReceivved] = useState<string>('')
|
||||
const [peerId, setPeerId] = useState<string>('')
|
||||
const [removepeerId, setRemovePeerId] = useState<string>('')
|
||||
const [theirPeerId, setTheirPeerId] = useState<string>('')
|
||||
const [myStream, setMyStream] = useState<MediaStream|null>(null);
|
||||
|
||||
const [devices, setDevices] = useState<MediaDeviceInfo[]>([])
|
||||
const [selectedDevice, setSelectedDevice] = useState<{ video: { deviceId: { exact: string } }, audio: { deviceId: { exact: string } } }>()
|
||||
|
||||
const handleFindPeers = () => {
|
||||
peerRef.current?.listAllPeers(peers => {
|
||||
setPeers(() => peers)
|
||||
console.log(`PEERS`, peers)
|
||||
if (peers.length > 1) {
|
||||
console.log(`CONNECT TO: `, peers.filter(a => a !== peerId)[0])
|
||||
if (!connection.current) {
|
||||
connection.current = peerRef.current?.connect(peers.filter(a => a !== peerId)[0])
|
||||
setRemovePeerId(() => peers.filter(a => a !== peerId)[0])
|
||||
}
|
||||
setFlowState(s => { return { ...s, flow: 'ROOM', color: 'blue', loading: true } })
|
||||
} else {
|
||||
setFlowState(s => { return { ...s, flow: 'NO_PEERS', color: 'blue', loading: true } })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const handleConnectServer = () => {
|
||||
setFlowState(s => { return { ...s, flow: 'SERVER' } })
|
||||
|
||||
peerRef.current = new Peer({
|
||||
host: "localhost",
|
||||
port: 4000,
|
||||
path: "/myapp",
|
||||
secure: false, // Use true with HTTPS
|
||||
})
|
||||
|
||||
// peerRef.current.socket.on('message')
|
||||
peerRef.current.on("open", function (id) {
|
||||
setPeerId(id)
|
||||
setFlowState(s => { return { ...s, color: 'green', loading: false } })
|
||||
console.log("My peer ID is: " + id, 'Connected Peers:',);
|
||||
handleFindPeers()
|
||||
});
|
||||
|
||||
peerRef.current.on('error', function (err) {
|
||||
setFlowState(s => { return { ...s, color: 'red', loading: false } })
|
||||
})
|
||||
peerRef.current.on('close', function () {
|
||||
console.log(`Closing connection`)
|
||||
})
|
||||
peerRef.current.on('connection', function (conn) {
|
||||
console.log(`Got Connection`, conn)
|
||||
// handleFindPeers()
|
||||
setFlowState(s => { return { ...s, flow: 'ROOM' } })
|
||||
connection.current!.on("data", function (data) {
|
||||
// setReceivved(data as string)
|
||||
console.log("Received", data);
|
||||
});
|
||||
conn.send("Hello!");
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
handleConnectServer()
|
||||
}, [])
|
||||
|
||||
|
||||
// const peer = new Peer({
|
||||
// host: "localhost",
|
||||
// port: 4000,
|
||||
// path: "/myapp",
|
||||
// secure: false, // Use true with HTTPS
|
||||
// });
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// peer.on("open", function (id) {
|
||||
// setPeerId(id)
|
||||
// console.log("My peer ID is: " + id);
|
||||
// });
|
||||
// }, [])
|
||||
|
||||
|
||||
// peer.on('connection', function (conn) {
|
||||
// console.log(`Got Connection`, conn)
|
||||
// conn.on("data", function (data) {
|
||||
// setReceivved(data as string)
|
||||
// console.log("Received", data);
|
||||
// });
|
||||
// conn.send("Hello!");
|
||||
// });
|
||||
// useEffect(() => {
|
||||
// // Get user media
|
||||
// navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// .then(stream => {
|
||||
// // setMyStream(stream);
|
||||
// // if (videoRef.current) {
|
||||
// // videoRef.current.srcObject = stream;
|
||||
// // }
|
||||
// })
|
||||
// .catch(err => console.error("Failed to get local stream", err));
|
||||
|
||||
// // Initialize PeerJS
|
||||
// // const peer = new Peer(); // PeerJS manages server connection
|
||||
// // currentPeer.current = peer;
|
||||
|
||||
// peer.on('open', (id) => {
|
||||
// setPeerId(id);
|
||||
// console.log('My peer ID is:', id);
|
||||
// });
|
||||
|
||||
// // Listen for incoming calls
|
||||
// peer.on('call', (call) => {
|
||||
// if (myStream) {
|
||||
// // Answer the call with your stream
|
||||
// call.answer(myStream);
|
||||
// call.on('stream', (remoteStream) => {
|
||||
// // Show stream in remote video element
|
||||
// if (videoRef.current) {
|
||||
// videoRef.current.srcObject = remoteStream;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
// peer.on('error', (err) => console.error(err));
|
||||
|
||||
// return () => {
|
||||
// if (peer) {
|
||||
// peer.destroy();
|
||||
// }
|
||||
// };
|
||||
// }, [myStream]);
|
||||
|
||||
|
||||
// useEffect(() => {
|
||||
// const load = async () => {
|
||||
// // Load media devices
|
||||
// const res = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// // .then(async res =>
|
||||
// setDevices(await navigator.mediaDevices.enumerateDevices())
|
||||
// // )
|
||||
// // setFlowIdx(1)
|
||||
// // setFlowIdx(2)
|
||||
|
||||
// // console.log(await navigator.mediaDevices.enumerateDevices())
|
||||
// // console.log(await navigator.mediaDevices.getUserMedia().)
|
||||
// }
|
||||
// load()
|
||||
// peer.on("call", function (call) {
|
||||
// alert(`You have a call`)
|
||||
// // Answer the call, providing our mediaStream
|
||||
// // if (mediaStream) {
|
||||
// call.answer(mediaStream);
|
||||
|
||||
// call.on('stream', (remoteStream) => {
|
||||
// // Show stream in remote video element
|
||||
// if (videoRef.current) {
|
||||
// videoRef.current.srcObject = remoteStream;
|
||||
// }
|
||||
// });
|
||||
// // }
|
||||
// });
|
||||
// }, [])
|
||||
|
||||
if (flowState.flow === 'ROOM') {
|
||||
return <div className='flex flex-col w-screen h-screen'>
|
||||
<div className='flex justify-between w-full flex-col'>
|
||||
<div>LOCAL PEER:{peerId}</div>
|
||||
<div>REMOVE PEER:{removepeerId}</div>
|
||||
</div>
|
||||
<div className='grow'></div>
|
||||
<form onSubmit={(form) => {
|
||||
form.preventDefault()
|
||||
const formData = new FormData(form.target);
|
||||
// const det = Object.fromEntries(formData.entries())
|
||||
try {
|
||||
console.log(`Sending:`, connection.current, formData.get('message') as string)
|
||||
connection.current?.send(formData.get('message') as string)
|
||||
} catch (e) {
|
||||
console.error('Failed to send message', e)
|
||||
}
|
||||
}}>
|
||||
<input name='message' placeholder='message' />
|
||||
<button type='submit'>Send</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
return (<Screen
|
||||
color={flowState.color}
|
||||
loading={flowState.loading}>
|
||||
{
|
||||
{
|
||||
'MIC': <SelectMicrophone setFlowState={setFlowState}
|
||||
onChange={e => {
|
||||
setSelectedDevice((d: any) => {
|
||||
return { ...d, audio: { deviceId: { exact: e } } }
|
||||
})
|
||||
setFlowState(s => { return { ...s, flow: 'VIDEO' } })
|
||||
// setFlowSection('VIDEO')
|
||||
}}
|
||||
/>,
|
||||
'VIDEO': <SelectVideo setFlowState={setFlowState}
|
||||
onChange={e => {
|
||||
setSelectedDevice((d: any) => {
|
||||
return { ...d, video: { deviceId: { exact: e } } }
|
||||
})
|
||||
handleConnectServer()
|
||||
}}
|
||||
/>,
|
||||
'SERVER': <ConnectingToSerever
|
||||
setFlowState={setFlowState}
|
||||
/>,
|
||||
'NO_PEERS': <>Waiting for others to join</>,
|
||||
'ROOM': <></>
|
||||
}[flowState.flow]
|
||||
}
|
||||
{/* {flow[flowIdx].screen} */}
|
||||
</Screen>)
|
||||
// return (
|
||||
// <div>
|
||||
// {JSON.stringify(peerId)}
|
||||
// <div>
|
||||
// <b>Settings</b>
|
||||
// <b>Video</b>
|
||||
// {devices.filter(a => a.kind === 'videoinput').map(device =>
|
||||
// <div key={device.deviceId}
|
||||
// onClick={() => setSelectedDevice((d: any) => {
|
||||
// return { ...d, video: { deviceId: { exact: device.deviceId } } }
|
||||
// })}
|
||||
// className={`${selectedDevice && selectedDevice.video && device.deviceId === selectedDevice?.video.deviceId.exact ? 'bg-green-300' : ''}`}
|
||||
// >
|
||||
// {device.label}
|
||||
// </div>
|
||||
// )}
|
||||
// <b>Audio</b>
|
||||
// {devices.filter(a => a.kind === 'audioinput').map(device =>
|
||||
// <div key={device.deviceId}
|
||||
// onClick={() => setSelectedDevice((d: any) => {
|
||||
// return { ...d, audio: { deviceId: { exact: device.deviceId } } }
|
||||
// })}
|
||||
// className={`${selectedDevice && selectedDevice.audio && device.deviceId === selectedDevice?.audio.deviceId.exact ? 'bg-green-300' : ''}`}
|
||||
|
||||
// >
|
||||
// {device.label}
|
||||
// </div>
|
||||
// )}
|
||||
// </div>
|
||||
// <input placeholder='Cannect to' onChange={e => setTheirPeerId(e.target.value)} />
|
||||
// <button onClick={() => {
|
||||
// console.log(`Connecting`)
|
||||
// connection.current
|
||||
// = peer.connect(theirPeerId, {
|
||||
|
||||
// });
|
||||
// }}>Start</button>
|
||||
// <button
|
||||
// className='p-2'
|
||||
// onClick={() => {
|
||||
// // const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
||||
|
||||
// navigator.mediaDevices.getUserMedia(selectedDevice)
|
||||
// .then(stream => peer.call(theirPeerId, stream))
|
||||
// // const call = peer.call(theirPeerId, stream);
|
||||
|
||||
// // const stream = await navigator.mediaDevices.getUserMedia({
|
||||
// // video: { deviceId: { exact: selectedVideoId } }
|
||||
// // });
|
||||
// }}
|
||||
// >Call</button>
|
||||
// <textarea onChange={e => connection.current?.send(e.target.value)} placeholder='Message' rows={6} />
|
||||
// {JSON.stringify(received)}
|
||||
// <video ref={videoRef} />
|
||||
// </div>
|
||||
// )
|
||||
}
|
||||
|
||||
|
||||
export function Screen({ color, loading, children }: {
|
||||
color: 'red' | 'blue' | 'green' | 'orange',
|
||||
loading: boolean
|
||||
children: ReactNode
|
||||
}) {
|
||||
return <div
|
||||
style={{
|
||||
background: color == 'orange' ? '#441306' : color == 'green' ? '#032e15' : color == 'red' ? '#460809' : '#162456'
|
||||
}}
|
||||
className={`h-screen w-screen flex items-center align-middle justify-center`}>
|
||||
<>
|
||||
<div className='relative '>
|
||||
{/* <div className='absolute flex items-center align-middle justify-center -left-20 -top-10 bg-blue-900 hover:bg-blue-800 shadow shadow-white shadow-2xl rounded-full grow w-64 h-64'>
|
||||
ITEM 1
|
||||
</div>
|
||||
<div className='absolute right-0 bg-blue-950 hover:bg-blue-900 w-64 h-64'>
|
||||
ITEM 2
|
||||
</div>
|
||||
<div className='absolute bottom-0 bg-blue-950 hover:bg-blue-900'>
|
||||
ITEM 3
|
||||
</div> */}
|
||||
<div
|
||||
style={{
|
||||
background: color == 'orange' ?
|
||||
'radial-gradient(circle, oklch(40.8% 0.123 38.172) 0%, rgba(0,0,0,0.3) 100%)'
|
||||
: color == 'green' ?
|
||||
'radial-gradient(circle, oklch(39.3% 0.095 152.535) 0%, rgba(0,0,0,0.3) 100%)' :
|
||||
color == 'red' ?
|
||||
'radial-gradient(circle,oklch(39.6% 0.141 25.723) 0%, rgba(0,0,0,0.3) 100%)'
|
||||
:
|
||||
'radial-gradient(circle,oklch(37.9% 0.146 265.522) 0%, rgba(0,0,0,0.3) 100%)'
|
||||
|
||||
}}
|
||||
className={`w-96 h-95 rounded-full relative border-8 shadow-lg ${loading && `animate-spin shadow-purple-300/75`} text-3xl text-white flex items-center justify-center align-middle `}>
|
||||
<div className="absolute inset-0 flex items-center justify-center animate-[inherit] direction-reverse">
|
||||
<span className="flex gap-2 flex-col items-center font-bold text-white text-shadow-white text-shadow-2xs">
|
||||
{children}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
}
|
||||
|
||||
export function ConnectingToSerever({
|
||||
setFlowState,
|
||||
}: {
|
||||
setFlowState: React.Dispatch<React.SetStateAction<FlowStateType>>
|
||||
}) {
|
||||
useEffect(() => { setFlowState(s => { return { ...s, color: 'blue', loading: true } }) }, [])
|
||||
|
||||
|
||||
// const peerConn = useCallback(() => {
|
||||
|
||||
// const peer = new Peer({
|
||||
// host: "localhost",
|
||||
// port: 4000,
|
||||
// path: "/myapp",
|
||||
// secure: false, // Use true with HTTPS
|
||||
// });
|
||||
// peer.on("open", function (id) {
|
||||
// setPeerId(id)
|
||||
// setColor('green')
|
||||
// console.log("My peer ID is: " + id);
|
||||
// });
|
||||
|
||||
// peer.on('error', function (err) {
|
||||
// setColor('red')
|
||||
// setLoading(false)
|
||||
// })
|
||||
|
||||
// peer.on('connection', function (conn) {
|
||||
// console.log(`Got Connection`, conn)
|
||||
// conn.on("data", function (data) {
|
||||
// // setReceivved(data as string)
|
||||
// console.log("Received", data);
|
||||
// });
|
||||
// conn.send("Hello!");
|
||||
// });
|
||||
// }, [])
|
||||
|
||||
// useEffect(() => {
|
||||
// const load = async () => {
|
||||
// peer.on("open", function (id) {
|
||||
// setPeerId(id)
|
||||
// setColor('green')
|
||||
// console.log("My peer ID is: " + id);
|
||||
// });
|
||||
|
||||
// peer.on('error', function (err) {
|
||||
// setColor('red')
|
||||
// setLoading(false)
|
||||
// })
|
||||
|
||||
// peer.on('connection', function (conn) {
|
||||
// console.log(`Got Connection`, conn)
|
||||
// conn.on("data", function (data) {
|
||||
// // setReceivved(data as string)
|
||||
// console.log("Received", data);
|
||||
// });
|
||||
// conn.send("Hello!");
|
||||
// });
|
||||
|
||||
// }
|
||||
// setColor('green')
|
||||
// setLoading(true)
|
||||
// load()
|
||||
// }, [])
|
||||
return <>
|
||||
<Server size={45} />
|
||||
Connecting to Server
|
||||
</>
|
||||
}
|
||||
|
||||
export function SelectMicrophone({
|
||||
setFlowState,
|
||||
onChange
|
||||
}: {
|
||||
setFlowState: React.Dispatch<React.SetStateAction<FlowStateType>>
|
||||
onChange: (device: string) => void
|
||||
}) {
|
||||
const [devices, setDevices] = useState<MediaDeviceInfo[]>([])
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
// Load media devices
|
||||
// setTimeout(() => setLoading(false), 3000)
|
||||
// setLoading(false)
|
||||
const res = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// // .then(async res =>
|
||||
setDevices(await navigator.mediaDevices.enumerateDevices())
|
||||
|
||||
setFlowState(s => { return { ...s, color: 'orange', loading: false } })
|
||||
}
|
||||
setFlowState(s => { return { ...s, color: 'green', loading: true } })
|
||||
load()
|
||||
}, [])
|
||||
return <>
|
||||
<MicrochipIcon size={45} />
|
||||
{
|
||||
devices.length == 0 ? 'Finding Audio Devices' : <>
|
||||
|
||||
Select audio device
|
||||
<select className='text-sm w-64 bg-white p-2 text-black'
|
||||
onChange={e => onChange(e.target.value)}
|
||||
>
|
||||
<option>Select</option>
|
||||
{devices.filter(a => a.kind === 'audioinput').map((device, i) =>
|
||||
<option key={`device-${i}`} value={device.deviceId}>{device.label}</option>
|
||||
)}
|
||||
|
||||
</select>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
|
||||
export function SelectVideo({
|
||||
setFlowState,
|
||||
onChange
|
||||
}: {
|
||||
setFlowState: React.Dispatch<React.SetStateAction<FlowStateType>>
|
||||
onChange: (device: string) => void
|
||||
}) {
|
||||
const [devices, setDevices] = useState<MediaDeviceInfo[]>([])
|
||||
useEffect(() => {
|
||||
const load = async () => {
|
||||
// Load media devices
|
||||
// setTimeout(() => setLoading(false), 3000)
|
||||
// setLoading(false)
|
||||
const res = await navigator.mediaDevices.getUserMedia({ video: true, audio: true })
|
||||
// // .then(async res =>
|
||||
setDevices(await navigator.mediaDevices.enumerateDevices())
|
||||
setFlowState(s => { return { ...s, color: 'orange', loading: false } })
|
||||
}
|
||||
setFlowState(s => { return { ...s, color: 'green', loading: true } })
|
||||
load()
|
||||
}, [])
|
||||
return <>
|
||||
<VideoIcon size={45} />
|
||||
{
|
||||
devices.length == 0 ? 'Finding Video Devices' : <>
|
||||
|
||||
Select video device
|
||||
<select className='text-sm w-64 bg-white p-2 text-black'
|
||||
onChange={e => onChange(e.target.value)}
|
||||
>
|
||||
<option>Select</option>
|
||||
{devices.filter(a => a.kind === 'videoinput').map((device, i) =>
|
||||
<option key={`device-${i}`} value={device.deviceId}>{device.label}</option>
|
||||
)}
|
||||
|
||||
</select>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
'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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const startButton = document.getElementById('startButton');
|
||||
const callButton = document.getElementById('callButton');
|
||||
const hangupButton = document.getElementById('hangupButton');
|
||||
callButton.disabled = true;
|
||||
hangupButton.disabled = true;
|
||||
startButton.addEventListener('click', start);
|
||||
callButton.addEventListener('click', call);
|
||||
hangupButton.addEventListener('click', hangup);
|
||||
|
||||
let startTime;
|
||||
const localVideo = document.getElementById('localVideo');
|
||||
const remoteVideo = document.getElementById('remoteVideo');
|
||||
|
||||
localVideo.addEventListener('loadedmetadata', function() {
|
||||
console.log(`Local video videoWidth: ${this.videoWidth}px, videoHeight: ${this.videoHeight}px`);
|
||||
});
|
||||
|
||||
remoteVideo.addEventListener('loadedmetadata', function() {
|
||||
console.log(`Remote video videoWidth: ${this.videoWidth}px, videoHeight: ${this.videoHeight}px`);
|
||||
});
|
||||
|
||||
remoteVideo.addEventListener('resize', () => {
|
||||
console.log(`Remote video size changed to ${remoteVideo.videoWidth}x${remoteVideo.videoHeight} - Time since pageload ${performance.now().toFixed(0)}ms`);
|
||||
// We'll use the first onsize callback as an indication that video has started
|
||||
// playing out.
|
||||
if (startTime) {
|
||||
const elapsedTime = window.performance.now() - startTime;
|
||||
console.log('Setup time: ' + elapsedTime.toFixed(3) + 'ms');
|
||||
startTime = null;
|
||||
}
|
||||
});
|
||||
|
||||
let localStream;
|
||||
let pc1;
|
||||
let pc2;
|
||||
const offerOptions = {
|
||||
offerToReceiveAudio: 1,
|
||||
offerToReceiveVideo: 1
|
||||
};
|
||||
|
||||
function getName(pc) {
|
||||
return (pc === pc1) ? 'pc1' : 'pc2';
|
||||
}
|
||||
|
||||
function getOtherPc(pc) {
|
||||
return (pc === pc1) ? pc2 : pc1;
|
||||
}
|
||||
|
||||
async function start() {
|
||||
console.log('Requesting local stream');
|
||||
startButton.disabled = true;
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
|
||||
console.log('Received local stream');
|
||||
localVideo.srcObject = stream;
|
||||
localStream = stream;
|
||||
callButton.disabled = false;
|
||||
} catch (e) {
|
||||
alert(`getUserMedia() error: ${e.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function call() {
|
||||
callButton.disabled = true;
|
||||
hangupButton.disabled = false;
|
||||
console.log('Starting call');
|
||||
startTime = window.performance.now();
|
||||
const videoTracks = localStream.getVideoTracks();
|
||||
const audioTracks = localStream.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 = {};
|
||||
console.log('RTCPeerConnection configuration:', configuration);
|
||||
pc1 = new RTCPeerConnection(configuration);
|
||||
console.log('Created local peer connection object pc1');
|
||||
pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));
|
||||
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);
|
||||
|
||||
localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
|
||||
console.log('Added local stream to pc1');
|
||||
|
||||
try {
|
||||
console.log('pc1 createOffer start');
|
||||
const offer = await pc1.createOffer(offerOptions);
|
||||
await onCreateOfferSuccess(offer);
|
||||
} catch (e) {
|
||||
onCreateSessionDescriptionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
function onCreateSessionDescriptionError(error) {
|
||||
console.log(`Failed to create session description: ${error.toString()}`);
|
||||
}
|
||||
|
||||
async function onCreateOfferSuccess(desc) {
|
||||
console.log(`Offer from pc1\n${desc.sdp}`);
|
||||
console.log('pc1 setLocalDescription start');
|
||||
try {
|
||||
await pc1.setLocalDescription(desc);
|
||||
onSetLocalSuccess(pc1);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError();
|
||||
}
|
||||
|
||||
console.log('pc2 setRemoteDescription start');
|
||||
try {
|
||||
await pc2.setRemoteDescription(desc);
|
||||
onSetRemoteSuccess(pc2);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError();
|
||||
}
|
||||
|
||||
console.log('pc2 createAnswer start');
|
||||
// Since the 'remote' side has no media stream we need
|
||||
// to pass in the right constraints in order for it to
|
||||
// accept the incoming offer of audio and video.
|
||||
try {
|
||||
const answer = await pc2.createAnswer();
|
||||
await onCreateAnswerSuccess(answer);
|
||||
} catch (e) {
|
||||
onCreateSessionDescriptionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
function onSetLocalSuccess(pc) {
|
||||
console.log(`${getName(pc)} setLocalDescription complete`);
|
||||
}
|
||||
|
||||
function onSetRemoteSuccess(pc) {
|
||||
console.log(`${getName(pc)} setRemoteDescription complete`);
|
||||
}
|
||||
|
||||
function onSetSessionDescriptionError(error) {
|
||||
console.log(`Failed to set session description: ${error.toString()}`);
|
||||
}
|
||||
|
||||
function gotRemoteStream(e) {
|
||||
if (remoteVideo.srcObject !== e.streams[0]) {
|
||||
remoteVideo.srcObject = e.streams[0];
|
||||
console.log('pc2 received remote stream');
|
||||
}
|
||||
}
|
||||
|
||||
async function onCreateAnswerSuccess(desc) {
|
||||
console.log(`Answer from pc2:\n${desc.sdp}`);
|
||||
console.log('pc2 setLocalDescription start');
|
||||
try {
|
||||
await pc2.setLocalDescription(desc);
|
||||
onSetLocalSuccess(pc2);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError(e);
|
||||
}
|
||||
console.log('pc1 setRemoteDescription start');
|
||||
try {
|
||||
await pc1.setRemoteDescription(desc);
|
||||
onSetRemoteSuccess(pc1);
|
||||
} catch (e) {
|
||||
onSetSessionDescriptionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
async function onIceCandidate(pc, 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)'}`);
|
||||
}
|
||||
|
||||
function onAddIceCandidateSuccess(pc) {
|
||||
console.log(`${getName(pc)} addIceCandidate success`);
|
||||
}
|
||||
|
||||
function onAddIceCandidateError(pc, error) {
|
||||
console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
|
||||
}
|
||||
|
||||
function onIceStateChange(pc, event) {
|
||||
if (pc) {
|
||||
console.log(`${getName(pc)} ICE state: ${pc.iceConnectionState}`);
|
||||
console.log('ICE state change event: ', event);
|
||||
}
|
||||
}
|
||||
|
||||
function hangup() {
|
||||
console.log('Ending call');
|
||||
pc1.close();
|
||||
pc2.close();
|
||||
pc1 = null;
|
||||
pc2 = null;
|
||||
hangupButton.disabled = true;
|
||||
callButton.disabled = false;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { io } from 'socket.io-client';
|
||||
|
||||
// "undefined" means the URL will be computed from the window.location object
|
||||
const URL = process.env.NODE_ENV === 'production' ? 'http://192.168.0.131:4000' : 'http://192.168.0.131:4000';
|
||||
|
||||
export const socket = io(URL);
|
||||
Reference in new issue
Block a user