package main import ( "encoding/json" "github.com/google/uuid" "github.com/gorilla/websocket" "github.com/pion/webrtc/v4" ) type Signal struct { Type string `json:"type"` Data json.RawMessage `json:"data"` } type Peer struct { ID string Conn *websocket.Conn PC *webrtc.PeerConnection Room *Room } func NewPeer(conn *websocket.Conn, room *Room) *Peer { pc, err := webrtc.NewPeerConnection(webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ {URLs: []string{"stun:stun.l.google.com:19302"}}, }, }) if err != nil { panic(err) } // 🔥 ОБЯЗАТЕЛЬНО для SFU _, _ = pc.AddTransceiverFromKind( webrtc.RTPCodecTypeAudio, webrtc.RTPTransceiverInit{ Direction: webrtc.RTPTransceiverDirectionSendrecv, }, ) _, _ = pc.AddTransceiverFromKind( webrtc.RTPCodecTypeVideo, webrtc.RTPTransceiverInit{ Direction: webrtc.RTPTransceiverDirectionSendrecv, }, ) p := &Peer{ ID: uuid.NewString(), Conn: conn, PC: pc, Room: room, } pc.OnICECandidate(func(c *webrtc.ICECandidate) { if c == nil { return } data, _ := json.Marshal(c.ToJSON()) p.Send("ice", data) }) pc.OnTrack(func(remote *webrtc.TrackRemote, _ *webrtc.RTPReceiver) { local := NewTrack(remote) // 🔥 регистрируем трек в комнате p.Room.AddTrack(p, local) for { pkt, _, err := remote.ReadRTP() if err != nil { return } local.Write(pkt) } }) return p } func (p *Peer) AddTrack(track *Track) { _, err := p.PC.AddTrack(track.Local) if err != nil { return } p.Send("renegotiate", nil) } func (p *Peer) ReadLoop() { defer func() { p.Conn.Close() p.Room.RemovePeer(p.ID) p.PC.Close() }() for { var msg Signal if err := p.Conn.ReadJSON(&msg); err != nil { return } switch msg.Type { case "offer": var offer webrtc.SessionDescription json.Unmarshal(msg.Data, &offer) p.PC.SetRemoteDescription(offer) answer, _ := p.PC.CreateAnswer(nil) p.PC.SetLocalDescription(answer) data, _ := json.Marshal(answer) p.Send("answer", data) case "ice": var c webrtc.ICECandidateInit json.Unmarshal(msg.Data, &c) p.PC.AddICECandidate(c) } } } func (p *Peer) Send(t string, data []byte) { p.Conn.WriteJSON(Signal{Type: t, Data: data}) }