506 lines
18 KiB
Plaintext
506 lines
18 KiB
Plaintext
|
|
/* 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>
|
||
|
|
</>
|
||
|
|
}
|
||
|
|
</>
|
||
|
|
}
|