From 013670b78f9e0c795021f3891ba3327cb728555a Mon Sep 17 00:00:00 2001 From: loveuer Date: Thu, 22 May 2025 17:57:36 +0800 Subject: [PATCH] =?UTF-8?q?wip:=200.2.4=20=20=20=E8=BF=98=E6=9C=AA?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=20rtc=20=E6=8F=A1=E6=89=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/page/local.tsx | 80 +++++++++++++++++++++++++++++++------ internal/api/api.go | 1 + internal/controller/room.go | 23 +++++++++-- internal/handler/local.go | 23 +++++++++++ 4 files changed, 112 insertions(+), 15 deletions(-) diff --git a/frontend/src/page/local.tsx b/frontend/src/page/local.tsx index bd8281c..3adc5b4 100644 --- a/frontend/src/page/local.tsx +++ b/frontend/src/page/local.tsx @@ -3,6 +3,7 @@ import {useEffect, useState} from "react"; import {createUseStyles} from "react-jss"; import {useWebsocket} from "../hook/websocket/u-ws.tsx"; import {Resp} from "../interface/response.ts"; +import {message} from "../hook/message/u-message.tsx"; const useClass = createUseStyles({ '@global': { @@ -68,6 +69,12 @@ interface Bubble { angle: number; // 新增角度属性 } +interface WSMessage { + body: Client; + time: number; + type: "enter" | "leave" | "offer" | "answer" +} + interface Client { client_type: 'desktop' | 'mobile' | 'tablet'; app_type: 'web'; @@ -83,6 +90,7 @@ interface Store { client: Client | null clients: Client[] rtc: RTCPeerConnection | null + ch: RTCDataChannel | null offer: RTCSessionDescription | null candidate: RTCIceCandidate | null } @@ -98,6 +106,7 @@ function handleFileChunk(chunk: any) { console.log("[D] rtc file chunk =", chunk) } + export const LocalSharing: React.FC = () => { const classes = useClass(); const [rtcStore, setRTCStore] = useState({} as Store) @@ -175,6 +184,46 @@ 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 + console.log('[D] update clients called, resp =', jes) + setRTCStore(val => { + return {...val, clients: jes.data} + }) + } + + const handleWSEvent = async (e: MessageEvent) => { + const msg = JSON.parse(e.data) as WSMessage + console.log('[D] ws event msg =', msg) + switch (msg.type) { + case "enter": + await updateClients() + break + case "leave": + 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 answer = await rtcStore.rtc?.createAnswer() + await rtcStore.rtc?.setLocalDescription(answer) + console.log('[D] answer =', answer) + await fetch("/api/ulocal/answer", { + method: "POST", + headers: {"Content-Type": "application/json"}, + body: JSON.stringify({id: offer.id, room: offer.room, 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 + } + } + useEffect(() => { const fn = async () => { const rtc = new RTCPeerConnection({iceServers: [{urls: "stun:stun.qq.com:3478"}]}) @@ -193,12 +242,10 @@ export const LocalSharing: React.FC = () => { } }) - console.log('[D] rtc step 1') await waitNegotiationneeded const candidate: RTCIceCandidate | null = await waitCandidate; if (!candidate) throw new Error("candidate is null") - console.log('[D] rtc step 2') const res = await fetch("/api/ulocal/register", { method: "POST", @@ -206,19 +253,21 @@ export const LocalSharing: React.FC = () => { body: JSON.stringify({candidate: candidate, offer: rtc.localDescription}) }) const jes = await res.json() as Resp; + + if (!jes.data.id) { + message.error("注册失败") + throw new Error("register failed") + } + setRTCStore(val => { - return {...val, client: jes.data, candidate: candidate, offer: rtc.localDescription} + 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] websocker url =', api) - connect(api, {}) + //console.log('[D] websocket url =', api) + connect(api, {fn: handleWSEvent}) - const res2 = await fetch(`/api/ulocal/clients?room=${jes.data.room}`) - const jes2 = await res2.json() as Resp - setRTCStore(val => { - return {...val, clients: jes2.data} - }) + await updateClients(jes.data.room) } fn() @@ -228,8 +277,15 @@ export const LocalSharing: React.FC = () => { // 气泡点击处理 const handleBubbleClick = async (id: string) => { - console.log('[D] click bubble!!!', id) - // await link(id) + 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
diff --git a/internal/api/api.go b/internal/api/api.go index 0097892..cb7bf27 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -27,6 +27,7 @@ func Start(ctx context.Context) <-chan struct{} { api := app.Group("/api/ulocal") api.Post("/register", handler.LocalRegister()) api.Post("/offer", handler.LocalOffer()) + api.Post("/answer", handler.LocalAnswer()) api.Get("/clients", handler.LocalClients()) api.Get("/ws", handler.LocalWS()) } diff --git a/internal/controller/room.go b/internal/controller/room.go index a462b3f..c76b1a0 100644 --- a/internal/controller/room.go +++ b/internal/controller/room.go @@ -230,11 +230,9 @@ func (rc *roomController) Enter(conn *websocket.Conn, id string) { } func (rc *roomController) List(room string) []*roomClient { + log.Debug("controller.room: list room = %s", room) clientList := make([]*roomClient, 0) - rc.Lock() - defer rc.Unlock() - clients, ok := rc.rooms[room] if !ok { return clientList @@ -283,6 +281,25 @@ func (rc *roomController) Offer(room, id string, offer *RoomOffer) { rc.rooms[room][id].msgChan <- map[string]any{ "type": "offer", + "id": id, + "room": room, "offer": offer, } } + +func (rc *roomController) Answer(room, id string, answer *RoomOffer) { + if _, ok := rc.rooms[room]; !ok { + return + } + + if _, ok := rc.rooms[room][id]; !ok { + return + } + + rc.rooms[room][id].msgChan <- map[string]any{ + "type": "answer", + "id": id, + "room": room, + "answer": answer, + } +} diff --git a/internal/handler/local.go b/internal/handler/local.go index 48d95a0..bdcd55c 100644 --- a/internal/handler/local.go +++ b/internal/handler/local.go @@ -97,3 +97,26 @@ func LocalOffer() nf.HandlerFunc { return resp.Resp200(c, req.Offer) } } + +func LocalAnswer() nf.HandlerFunc { + return func(c *nf.Ctx) error { + type Req struct { + Room string `json:"room"` + Id string `json:"id"` + Answer *controller.RoomOffer `json:"answer"` + } + + var ( + err error + req = new(Req) + ) + + if err = c.BodyParser(req); err != nil { + return c.Status(http.StatusBadRequest).JSON(map[string]string{"err": err.Error()}) + } + + controller.RoomController.Answer(req.Room, req.Id, req.Answer) + + return resp.Resp200(c, req) + } +}