wip: 0.2.4
还未实现 rtc 握手
This commit is contained in:
parent
16e9d663f4
commit
013670b78f
@ -3,6 +3,7 @@ import {useEffect, useState} from "react";
|
|||||||
import {createUseStyles} from "react-jss";
|
import {createUseStyles} from "react-jss";
|
||||||
import {useWebsocket} from "../hook/websocket/u-ws.tsx";
|
import {useWebsocket} from "../hook/websocket/u-ws.tsx";
|
||||||
import {Resp} from "../interface/response.ts";
|
import {Resp} from "../interface/response.ts";
|
||||||
|
import {message} from "../hook/message/u-message.tsx";
|
||||||
|
|
||||||
const useClass = createUseStyles({
|
const useClass = createUseStyles({
|
||||||
'@global': {
|
'@global': {
|
||||||
@ -68,6 +69,12 @@ interface Bubble {
|
|||||||
angle: number; // 新增角度属性
|
angle: number; // 新增角度属性
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface WSMessage {
|
||||||
|
body: Client;
|
||||||
|
time: number;
|
||||||
|
type: "enter" | "leave" | "offer" | "answer"
|
||||||
|
}
|
||||||
|
|
||||||
interface Client {
|
interface Client {
|
||||||
client_type: 'desktop' | 'mobile' | 'tablet';
|
client_type: 'desktop' | 'mobile' | 'tablet';
|
||||||
app_type: 'web';
|
app_type: 'web';
|
||||||
@ -83,6 +90,7 @@ interface Store {
|
|||||||
client: Client | null
|
client: Client | null
|
||||||
clients: Client[]
|
clients: Client[]
|
||||||
rtc: RTCPeerConnection | null
|
rtc: RTCPeerConnection | null
|
||||||
|
ch: RTCDataChannel | null
|
||||||
offer: RTCSessionDescription | null
|
offer: RTCSessionDescription | null
|
||||||
candidate: RTCIceCandidate | null
|
candidate: RTCIceCandidate | null
|
||||||
}
|
}
|
||||||
@ -98,6 +106,7 @@ function handleFileChunk(chunk: any) {
|
|||||||
console.log("[D] rtc file chunk =", chunk)
|
console.log("[D] rtc file chunk =", chunk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const LocalSharing: React.FC = () => {
|
export const LocalSharing: React.FC = () => {
|
||||||
const classes = useClass();
|
const classes = useClass();
|
||||||
const [rtcStore, setRTCStore] = useState<Store>({} as Store)
|
const [rtcStore, setRTCStore] = useState<Store>({} as Store)
|
||||||
@ -175,6 +184,46 @@ export const LocalSharing: React.FC = () => {
|
|||||||
return bubbles;
|
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 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(() => {
|
useEffect(() => {
|
||||||
const fn = async () => {
|
const fn = async () => {
|
||||||
const rtc = new RTCPeerConnection({iceServers: [{urls: "stun:stun.qq.com:3478"}]})
|
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
|
await waitNegotiationneeded
|
||||||
const candidate: RTCIceCandidate | null = await waitCandidate;
|
const candidate: RTCIceCandidate | null = await waitCandidate;
|
||||||
if (!candidate) throw new Error("candidate is null")
|
if (!candidate) throw new Error("candidate is null")
|
||||||
console.log('[D] rtc step 2')
|
|
||||||
|
|
||||||
const res = await fetch("/api/ulocal/register", {
|
const res = await fetch("/api/ulocal/register", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -206,19 +253,21 @@ export const LocalSharing: React.FC = () => {
|
|||||||
body: JSON.stringify({candidate: candidate, offer: rtc.localDescription})
|
body: JSON.stringify({candidate: candidate, offer: rtc.localDescription})
|
||||||
})
|
})
|
||||||
const jes = await res.json() as Resp<Client>;
|
const jes = await res.json() as Resp<Client>;
|
||||||
|
|
||||||
|
if (!jes.data.id) {
|
||||||
|
message.error("注册失败")
|
||||||
|
throw new Error("register failed")
|
||||||
|
}
|
||||||
|
|
||||||
setRTCStore(val => {
|
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}`
|
const api = `${window.location.protocol === 'https' ? 'wss' : 'ws'}://${window.location.host}/api/ulocal/ws?id=${jes.data.id}`
|
||||||
console.log('[D] websocker url =', api)
|
//console.log('[D] websocket url =', api)
|
||||||
connect(api, {})
|
connect(api, {fn: handleWSEvent})
|
||||||
|
|
||||||
const res2 = await fetch(`/api/ulocal/clients?room=${jes.data.room}`)
|
await updateClients(jes.data.room)
|
||||||
const jes2 = await res2.json() as Resp<Client[]>
|
|
||||||
setRTCStore(val => {
|
|
||||||
return {...val, clients: jes2.data}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn()
|
fn()
|
||||||
@ -228,8 +277,15 @@ export const LocalSharing: React.FC = () => {
|
|||||||
|
|
||||||
// 气泡点击处理
|
// 气泡点击处理
|
||||||
const handleBubbleClick = async (id: string) => {
|
const handleBubbleClick = async (id: string) => {
|
||||||
console.log('[D] click bubble!!!', id)
|
await fetch('/api/ulocal/offer', {
|
||||||
// await link(id)
|
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}>
|
return <div className={classes.container}>
|
||||||
|
@ -27,6 +27,7 @@ func Start(ctx context.Context) <-chan struct{} {
|
|||||||
api := app.Group("/api/ulocal")
|
api := app.Group("/api/ulocal")
|
||||||
api.Post("/register", handler.LocalRegister())
|
api.Post("/register", handler.LocalRegister())
|
||||||
api.Post("/offer", handler.LocalOffer())
|
api.Post("/offer", handler.LocalOffer())
|
||||||
|
api.Post("/answer", handler.LocalAnswer())
|
||||||
api.Get("/clients", handler.LocalClients())
|
api.Get("/clients", handler.LocalClients())
|
||||||
api.Get("/ws", handler.LocalWS())
|
api.Get("/ws", handler.LocalWS())
|
||||||
}
|
}
|
||||||
|
@ -230,11 +230,9 @@ func (rc *roomController) Enter(conn *websocket.Conn, id string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (rc *roomController) List(room string) []*roomClient {
|
func (rc *roomController) List(room string) []*roomClient {
|
||||||
|
log.Debug("controller.room: list room = %s", room)
|
||||||
clientList := make([]*roomClient, 0)
|
clientList := make([]*roomClient, 0)
|
||||||
|
|
||||||
rc.Lock()
|
|
||||||
defer rc.Unlock()
|
|
||||||
|
|
||||||
clients, ok := rc.rooms[room]
|
clients, ok := rc.rooms[room]
|
||||||
if !ok {
|
if !ok {
|
||||||
return clientList
|
return clientList
|
||||||
@ -283,6 +281,25 @@ func (rc *roomController) Offer(room, id string, offer *RoomOffer) {
|
|||||||
|
|
||||||
rc.rooms[room][id].msgChan <- map[string]any{
|
rc.rooms[room][id].msgChan <- map[string]any{
|
||||||
"type": "offer",
|
"type": "offer",
|
||||||
|
"id": id,
|
||||||
|
"room": room,
|
||||||
"offer": offer,
|
"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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -97,3 +97,26 @@ func LocalOffer() nf.HandlerFunc {
|
|||||||
return resp.Resp200(c, req.Offer)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user