wip: 0.2.5

还未实现 rtc 握手
This commit is contained in:
loveuer
2025-05-23 18:01:16 +08:00
parent 013670b78f
commit b8645a68ed
4 changed files with 158 additions and 226 deletions

View File

@ -72,18 +72,18 @@ interface Bubble {
interface WSMessage {
body: Client;
time: number;
type: "enter" | "leave" | "offer" | "answer"
type: "register" | "enter" | "leave" | "offer" | "answer"
}
interface Client {
client_type: 'desktop' | 'mobile' | 'tablet';
app_type: 'web';
room: string;
ip: number;
name: string;
id: string;
register_at: string;
offer: RTCSessionDescription;
candidate: RTCIceCandidateInit;
}
interface Store {
@ -184,19 +184,26 @@ export const LocalSharing: React.FC = () => {
return bubbles;
};
const updateClients = async (room?: string) => {
const res = await fetch(`/api/ulocal/clients?room=${room ? room : rtcStore.client?.room}`)
const jes = await res.json() as Resp<Client[]>
console.log('[D] update clients called, resp =', jes)
setRTCStore(val => {
return {...val, clients: jes.data}
})
const updateClients = async () => {
setTimeout(async () => {
const res = await fetch(`/api/ulocal/clients`)
const jes = await res.json() as Resp<Client[]>
setRTCStore(val => {
return {...val, clients: jes.data}
})
}, 500)
}
const handleWSEvent = async (e: MessageEvent) => {
const msg = JSON.parse(e.data) as WSMessage
console.log('[D] ws event msg =', msg)
switch (msg.type) {
case "register":
const reg_resp = JSON.parse(e.data) as { body: Client }
setRTCStore(val => {
return {...val, client: reg_resp.body}
})
break
case "enter":
await updateClients()
break
@ -204,89 +211,119 @@ export const LocalSharing: React.FC = () => {
await updateClients()
break
case "offer":
console.log('[D] rtc =', rtcStore.rtc)
const offer = JSON.parse(e.data) as { offer: RTCSessionDescriptionInit, id: number, room: string }
console.log('[D] offer =', offer)
await rtcStore.rtc?.setRemoteDescription(offer.offer)
const res_offer = JSON.parse(e.data) as {
offer: RTCSessionDescriptionInit,
id: number,
candidate: RTCIceCandidateInit
}
console.log('[D] offer res =', res_offer)
await rtcStore.rtc?.setRemoteDescription(res_offer.offer)
const answer = await rtcStore.rtc?.createAnswer()
await rtcStore.rtc?.setLocalDescription(answer)
console.log('[D] answer =', answer)
await fetch("/api/ulocal/answer", {
method: "POST",
await fetch('/api/ulocal/answer', {
method: 'POST',
headers: {"Content-Type": "application/json"},
body: JSON.stringify({id: offer.id, room: offer.room, answer: answer})
body: JSON.stringify({id: rtcStore.client?.id, answer: answer})
})
break
case "answer":
// const _answer = JSON.parse(e.data) as { answer: RTCSessionDescriptionInit, id: number, room: string }
// await rtcStore.rtc?.setRemoteDescription(_answer.answer)
// break
const res_answer = JSON.parse(e.data) as { answer: RTCSessionDescriptionInit, id: number }
await rtcStore.rtc?.setRemoteDescription(res_answer.answer)
break
}
}
// useEffect(() => {
// const fn = async () => {
// // const rtc = new RTCPeerConnection({iceServers: [{urls: "stun:stun.qq.com:3478"}]})
// const rtc = new RTCPeerConnection({})
// rtc.onconnectionstatechange = () => {
// console.log('rtc connection state =', rtc.connectionState)
// }
// const dataChannel = rtc.createDataChannel('fileTransfer', {ordered: true});
// setupDataChannel(dataChannel);
// const waitCandidate = (): Promise<RTCIceCandidate | null> => {
// return new Promise<RTCIceCandidate | null>(resolve => {
// rtc.onicecandidate = (e) => {
// resolve(e.candidate)
// }
// })
// }
//
// const waitNegotiation = (): Promise<void> => {
// return new Promise<void>(resolve => {
// rtc.onnegotiationneeded = async () => {
// const _offer = await rtc.createOffer()
// await rtc.setLocalDescription(_offer)
// resolve()
// }
// })
// }
//
// await waitNegotiation()
// const candidate: RTCIceCandidate | null = await waitCandidate();
//
// const res = await fetch("/api/ulocal/register", {
// method: "POST",
// headers: {"Content-Type": "application/json"},
// body: JSON.stringify({candidate: candidate})
// })
// const jes = await res.json() as Resp<Client>;
//
// console.log('[D] register resp =', jes)
//
// if (!jes.data.id) {
// message.error("注册失败")
// throw new Error("register failed")
// }
//
// setRTCStore(val => {
// return {
// ...val,
// client: jes.data,
// candidate: candidate,
// offer: rtc.localDescription,
// rtc: rtc,
// }
// })
//
// const api = `${window.location.protocol === 'https' ? 'wss' : 'ws'}://${window.location.host}/api/ulocal/ws?id=${jes.data.id}`
// console.log('[D] websocket url =', api)
// connect(api, {fn: handleWSEvent})
//
// await updateClients()
// }
//
// fn()
//
// return () => close();
// }, []
// );
// 气泡点击处理
const handleBubbleClick = async (bubble: Bubble) => {
const offer = await rtcStore.rtc?.createOffer()
await rtcStore.rtc?.setLocalDescription(offer)
// await fetch('/api/ulocal/offer', {
// method: 'POST',
// headers: {"Content-Type": "application/json"},
// body: JSON.stringify({
// id: bubble.id,
// offer: rtcStore.offer,
// candidate: rtcStore.candidate,
// })
// })
};
useEffect(() => {
const fn = async () => {
connect("/api/ulocal/ws", {fn: handleWSEvent})
await updateClients()
const rtc = new RTCPeerConnection({iceServers: [{urls: "stun:stun.qq.com:3478"}]})
const dataChannel = rtc.createDataChannel('fileTransfer', {ordered: true});
setupDataChannel(dataChannel);
const waitCandidate = new Promise<RTCIceCandidate | null>(resolve => {
rtc.onicecandidate = (e) => {
resolve(e.candidate)
}
})
const waitNegotiationneeded = new Promise<void>(resolve => {
rtc.onnegotiationneeded = async () => {
const _offer = await rtc.createOffer()
await rtc.setLocalDescription(_offer)
resolve()
}
})
await waitNegotiationneeded
const candidate: RTCIceCandidate | null = await waitCandidate;
if (!candidate) throw new Error("candidate is null")
const res = await fetch("/api/ulocal/register", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({candidate: candidate, offer: rtc.localDescription})
})
const jes = await res.json() as Resp<Client>;
if (!jes.data.id) {
message.error("注册失败")
throw new Error("register failed")
}
setRTCStore(val => {
return {...val, client: jes.data, candidate: candidate, offer: rtc.localDescription, rtc: rtc, ch: dataChannel}
})
const api = `${window.location.protocol === 'https' ? 'wss' : 'ws'}://${window.location.host}/api/ulocal/ws?id=${jes.data.id}`
//console.log('[D] websocket url =', api)
connect(api, {fn: handleWSEvent})
await updateClients(jes.data.room)
}
setRTCStore(val => {return {...val, rtc: rtc}})
};
fn()
return () => close();
}, []);
// 气泡点击处理
const handleBubbleClick = async (id: string) => {
await fetch('/api/ulocal/offer', {
method: 'POST',
headers: {"Content-Type": "application/json"},
body: JSON.stringify({room: rtcStore.client?.room, id: id, offer: rtcStore.offer})
})
setTimeout(() => {
rtcStore.ch?.send("hello, world")
}, 1000)
};
}, [])
return <div className={classes.container}>
<CloudBackground/>
@ -305,7 +342,7 @@ export const LocalSharing: React.FC = () => {
`${Math.random() * 0.5}s,
${0.5 + Math.random() * 2}s`
}}
onClick={() => handleBubbleClick(bubble.id)}
onClick={() => handleBubbleClick(bubble)}
>
{bubble.name}
</div>