CoffeeChat/webapp/components/chat/WebRTCChatUsingPeer copy 3.tmp

530 lines
19 KiB
Plaintext
Raw Permalink Normal View History

2026-04-03 12:35:13 +02:00
/* 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>
</>
}
</>
}