package localserver import ( context "context" "encoding/json" "fmt" "io" sync "sync" "github.com/pion/webrtc/v3" ) type WebrtcFsManager struct { stream SignalingService_LinkClient DatachannelManager DataChannelManager LocalSD map[string]*webrtc.SessionDescription RTCPeerConnections map[string]*webrtc.PeerConnection DataChannels map[string]*webrtc.DataChannel PendingCandidates map[string][]*webrtc.ICECandidate CandidateChannel chan *IncomingCandidate CandidateMux *sync.RWMutex RTCPeerConnectionMapMux *sync.RWMutex DataChannelMapMux *sync.RWMutex LocalSDMapMux *sync.RWMutex } func NewWebrtcFsManager(dataChannelManager DataChannelManager) (webrtcFsManager *WebrtcFsManager, err error) { webrtcFsManager = &WebrtcFsManager{ DatachannelManager: dataChannelManager, DataChannels: make(map[string]*webrtc.DataChannel), PendingCandidates: make(map[string][]*webrtc.ICECandidate), LocalSD: make(map[string]*webrtc.SessionDescription), RTCPeerConnections: make(map[string]*webrtc.PeerConnection), RTCPeerConnectionMapMux: &sync.RWMutex{}, LocalSDMapMux: &sync.RWMutex{}, CandidateMux: &sync.RWMutex{}, DataChannelMapMux: &sync.RWMutex{}, } return } func (wf *WebrtcFsManager) sendSignalingMessage(messageType, from, to string, payload map[string]interface{}) (err error) { bs, err := json.Marshal(payload) if err != nil { return } err = wf.stream.Send(&SignalingMessage{ Type: messageType, From: from, To: to, Payload: bs, }) return } func (wf *WebrtcFsManager) CreateOffer(ctx context.Context, target string, from string, cb OnICECandidateFunc) (err error) { peerConnection, err := wf.createPeerConnection(target, from, webrtc.SDPTypeOffer, cb) if err != nil { return } rawOffer, err := peerConnection.CreateOffer(nil) if err != nil { return } if err = peerConnection.SetLocalDescription(rawOffer); err != nil { return } wf.RTCPeerConnectionMapMux.Lock() logger.Println("adding for target", target) wf.RTCPeerConnections[target] = peerConnection wf.RTCPeerConnectionMapMux.Unlock() err = wf.sendSignalingMessage(string(WEBRTC_OFFER_FS), "lolo_local_serv", target, map[string]any{ "to": target, "from": "lolo_local_serv", "sdp": rawOffer.SDP, }) return } func (wf *WebrtcFsManager) HandleOffer(ctx context.Context, req map[string]string, cb OnICECandidateFunc) (err error) { done, errCh := make(chan struct{}), make(chan error) go func() { peerConnection, err := wf.createPeerConnection(req[FROM], req[TO], webrtc.SDPTypeAnswer, cb) if err != nil { errCh <- err return } wf.RTCPeerConnectionMapMux.Lock() wf.RTCPeerConnections[req[FROM]] = peerConnection wf.RTCPeerConnectionMapMux.Unlock() offer := webrtc.SessionDescription{ Type: webrtc.SDPTypeOffer, SDP: req[SDP], } if err = peerConnection.SetRemoteDescription(offer); err != nil { errCh <- err return } rawAnswer, err := peerConnection.CreateAnswer(nil) if err != nil { errCh <- err return } wf.LocalSDMapMux.Lock() wf.LocalSD[req[FROM]] = &rawAnswer wf.LocalSDMapMux.Unlock() // if err = wf.stream.Send(&Request{ // Type: string(WEBRTC_ANSWER_FS), // From: "lolo_local_serv", // Token: "none", // Payload: map[string]string{ // "to": req[FROM], // "from": "lolo_local_serv", // "sdp": rawAnswer.SDP, // }, // }); err != nil { // errCh <- err // return // } done <- struct{}{} }() select { case <-done: return case err = <-errCh: return case <-ctx.Done(): err = ctx.Err() return } } func (wf *WebrtcFsManager) HandleAnswer(ctx context.Context, req map[string]string) (err error) { wf.RTCPeerConnectionMapMux.RLock() defer wf.RTCPeerConnectionMapMux.RUnlock() defer func() { if r := recover(); err != nil { logger.Printf("recover from panic : %v\n", r) } }() if _, ok := wf.RTCPeerConnections[req[FROM]]; !ok { err = fmt.Errorf("no corresponding peer connection for id : %s", req[FROM]) return } peerConnnection := wf.RTCPeerConnections[req[FROM]] if err = peerConnnection.SetRemoteDescription(webrtc.SessionDescription{ Type: webrtc.SDPTypeAnswer, SDP: req[SDP], }); err != nil { return } // if err = wf.stream.Send(&Request{ // Type: string(WEBRTC_COUNTER_OFFER_FS), // From: "lolo_local_serv", // Token: "none", // Payload: map[string]string{ // "from": "lolo_local_serv", // "to": req[FROM], // }, // }); err != nil { // return // } wf.CandidateMux.RLock() for range wf.PendingCandidates[req[FROM]] { logger.Println("sending candidate to", req[FROM]) // if err = wf.stream.Send(&Request{ // Type: string(WEBRTC_CANDIDATE_FS), // From: "lolo_local_serv", // Token: "none", // Payload: map[string]string{ // "from": "lolo_local_serv", // "to": req[FROM], // "candidate": candidate.ToJSON().Candidate, // "sdpMid": *candidate.ToJSON().SDPMid, // "sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), // }, // }); err != nil { // return // } } wf.CandidateMux.RUnlock() wf.CandidateMux.Lock() delete(wf.PendingCandidates, req[FROM]) wf.CandidateMux.Unlock() wf.LocalSDMapMux.Lock() delete(wf.LocalSD, req[FROM]) wf.LocalSDMapMux.Unlock() return } func (wf *WebrtcFsManager) HandleCounterOffer(ctx context.Context, req map[string]string) (err error) { wf.RTCPeerConnectionMapMux.RLock() if _, ok := wf.RTCPeerConnections[req[FROM]]; !ok { err = fmt.Errorf("no field corresponding peer connection for id %s", req[FROM]) return } connection := wf.RTCPeerConnections[req[FROM]] wf.RTCPeerConnectionMapMux.RUnlock() wf.LocalSDMapMux.RLock() if err = connection.SetLocalDescription(*wf.LocalSD[req[FROM]]); err != nil { return } wf.LocalSDMapMux.RUnlock() wf.CandidateMux.RLock() for range wf.PendingCandidates[req[FROM]] { // logger.Println("sending candidate to", req[FROM]) // if err = wf.stream.Send(&Request{ // Type: string(WEBRTC_CANDIDATE_FS), // From: "lolo_local_serv", // Token: "none", // Payload: map[string]string{ // "from": "lolo_local_serv", // "to": req[FROM], // "candidate": candidate.ToJSON().Candidate, // "sdpMid": *candidate.ToJSON().SDPMid, // "sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), // }, // }); err != nil { // return // } } wf.CandidateMux.RUnlock() return } func (wf *WebrtcFsManager) createPeerConnection(target string, from string, peerType webrtc.SDPType, cb OnICECandidateFunc) (peerConnection *webrtc.PeerConnection, err error) { defer func() { if r := recover(); err != nil { logger.Printf("recover from panic : %v\n", r) } }() s := webrtc.SettingEngine{} s.DetachDataChannels() api := webrtc.NewAPI(webrtc.WithSettingEngine(s)) config := webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { URLs: []string{"stun:stun.l.google.com:19302", "stun:stunserver.org:3478", "stun:stun.l.google.com:19302?transport=tcp"}, }, }, SDPSemantics: webrtc.SDPSemanticsUnifiedPlan, } peerConnection, err = api.NewPeerConnection(config) if err != nil { return } if peerType == webrtc.SDPTypeOffer { maxRetransmits := uint16(100) channel, err := peerConnection.CreateDataChannel("data", &webrtc.DataChannelInit{ MaxRetransmits: &maxRetransmits, }) if err != nil { return nil, err } channel.OnOpen(func() { logger.Println("channel opened") if chanErr := channel.SendText("yooo man this is open"); chanErr != nil { logger.Println(chanErr) } }) channel.OnMessage(func(msg webrtc.DataChannelMessage) { logger.Printf("new message %s\n", string(msg.Data)) done, errCh := wf.DatachannelManager.HandleMessage(&DatachannelMessage{ From: target, Type: "test", Payload: &DatachannelMessagePayload{}, }, channel) select { case <-done: //logger.Println("done with success") case e := <-errCh: logger.Println(e) logger.Println("this is impossible") } }) wf.DataChannelMapMux.Lock() wf.DataChannels[target] = channel wf.DataChannelMapMux.Unlock() } peerConnection.OnICEConnectionStateChange(func(is webrtc.ICEConnectionState) { logger.Printf("ICE connection state has changed %s\n", is.String()) if is == webrtc.ICEConnectionStateDisconnected || is == webrtc.ICEConnectionStateFailed { logger.Println(is) } }) peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) { dc.OnOpen(func() { logger.Printf("got a new open datachannel %s\n", dc.Label()) dataChann, err := dc.Detach() if err != nil { logger.Println(err) return } for { var x []byte = make([]byte, 2<<15) n, _, err := dataChann.ReadDataChannel(x) if err != nil { logger.Println(err) if err == io.EOF { return } continue } go func(msg []byte) { var dataChannelMessage DatachannelMessage if unmarshalErr := json.Unmarshal(msg, &dataChannelMessage); unmarshalErr != nil { logger.Println(unmarshalErr) return } done, errCh := wf.DatachannelManager.HandleMessage(&DatachannelMessage{ From: dataChannelMessage.From, Type: dataChannelMessage.Type, Payload: dataChannelMessage.Payload, }, dc) select { case <-done: //logger.Println("done with success") case e := <-errCh: logger.Println(e) logger.Println("this is impossible") } }(x[:n]) } }) // dc.OnMessage(func(msg webrtc.DataChannelMessage) { // var dataChannelMessage DatachannelMessage // if unmarshalErr := json.Unmarshal(msg.Data, &dataChannelMessage); unmarshalErr != nil { // logger.Println(unmarshalErr) // return // } // done, errCh := wf.DatachannelManager.HandleMessage(&DatachannelMessage{ // From: dataChannelMessage.From, // Type: dataChannelMessage.Type, // Payload: dataChannelMessage.Payload, // }, dc) // select { // case <-done: // //logger.Println("done with success") // case e := <-errCh: // logger.Println(e) // logger.Println("this is impossible") // } // }) }) peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) { if i == nil { return } wf.CandidateMux.Lock() defer wf.CandidateMux.Unlock() desc := peerConnection.RemoteDescription() if desc == nil { wf.PendingCandidates[target] = append(wf.PendingCandidates[target], i) } else { logger.Println(i) if iceCandidateErr := cb(target, i); iceCandidateErr != nil { logger.Println(iceCandidateErr) } } }) peerConnection.OnNegotiationNeeded(func() { if peerConnection.SignalingState() != webrtc.SignalingStateHaveLocalOffer && peerConnection.SignalingState() != webrtc.SignalingStateHaveRemoteOffer { wf.RTCPeerConnectionMapMux.Lock() defer wf.RTCPeerConnectionMapMux.Unlock() for _, connection := range wf.RTCPeerConnections { if connection.SignalingState() != webrtc.SignalingStateHaveLocalOffer && connection.SignalingState() != webrtc.SignalingStateHaveRemoteOffer { localSd, err := connection.CreateOffer(nil) if err != nil { logger.Println(err) return } if err = connection.SetLocalDescription(localSd); err != nil { logger.Println(err) return } // if err = wf.stream.Send(&Request{ // Type: string(WEBRTC_RENNEGOTIATION_OFFER_FS), // From: "lolo_local_serv", // Token: "", // Payload: map[string]string{ // "to": id, // "sdp": localSd.SDP, // }, // }); err != nil { // logger.Println(err) // return // } } } } }) return } func (wf *WebrtcFsManager) HandleRennegotiationOffer(from string, dst string, sdp string) (err error) { wf.RTCPeerConnectionMapMux.Lock() defer wf.RTCPeerConnectionMapMux.Unlock() if _, ok := wf.RTCPeerConnections[from]; !ok { err = fmt.Errorf("no corresponding peer connection for id %s", from) return } if wf.RTCPeerConnections[from].SignalingState() != webrtc.SignalingStateStable { err = fmt.Errorf("rennego called in wrong state") return } if err = wf.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil { return } localSd, err := wf.RTCPeerConnections[from].CreateAnswer(nil) if err != nil { return } if err = wf.RTCPeerConnections[from].SetLocalDescription(localSd); err != nil { return } // if err = wf.stream.Send(&Request{ // Type: string(WEBRTC_RENNEGOTIATION_ANSWER_FS), // From: "lolo_local_serv", // Token: "", // Payload: map[string]string{ // "to": from, // "sdp": localSd.SDP, // }, // }); err != nil { // logger.Println(err) // return // } return } func (wf *WebrtcFsManager) HandleRennegotiationAnswer(from string, dst string, sdp string) (err error) { wf.RTCPeerConnectionMapMux.Lock() defer wf.RTCPeerConnectionMapMux.Unlock() if _, ok := wf.RTCPeerConnections[from]; !ok { err = fmt.Errorf("no corresponding peer connection for id %s", from) return } err = wf.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer}) return } func (wf *WebrtcFsManager) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) { wf.RTCPeerConnectionMapMux.Lock() defer wf.RTCPeerConnectionMapMux.Unlock() err = wf.RTCPeerConnections[from].AddICECandidate(*candidate) return }