package localserver import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "strconv" sync "sync" "sync/atomic" "time" "github.com/google/uuid" "github.com/pion/rtcp" "github.com/pion/webrtc/v3" ) const ( NAME = "name" ID = "ID" SDP = "sdp" CANDIDATE = "webrtc_candidate" SQUAD_ID = "squadId" FROM = "from" TO = "to" STOP_CALL = "stop_call" LIST_HOSTED_SQUADS_BY_HOST = "list_hosted_squads_by_host" ) type Squad struct { ID string Members []string } type WebRTCCallManager struct { stream SignalingService_LinkClient middlewares []WebrtcCallEventManager ID string LocalSD map[string]*webrtc.SessionDescription RTCPeerConnections map[string]*RTCPeerConnection AudioTransceiver map[string][]*PeerSender VideoTransceiver map[string][]*PeerSender DataChannels map[string]*DataChannel PendingCandidates map[string][]*webrtc.ICECandidate RemoteTracks map[string][]*RemoteTrack Squads map[string]*Squad SquadMapMux *sync.RWMutex CandidateChannel chan *IncomingCandidate CandidateMux *sync.RWMutex RemoteTracksMux *sync.RWMutex RTCPeerConnectionMapMux *sync.RWMutex DataChannelMapMux *sync.RWMutex LocalSDMapMux *sync.RWMutex AudioSenderMux *sync.RWMutex VideoSenderMux *sync.RWMutex } type IncomingCandidate struct { For string Candidate *webrtc.ICECandidateInit } type RTCPeerConnection struct { *webrtc.PeerConnection id string makingOffer bool negotiate func(string, string) makingOfferLock *sync.Mutex } type DataChannel struct { DataChannel *webrtc.DataChannel bufferedAmountLowThresholdReached <-chan struct{} l *int32 } type PeerSender struct { ID string Transceiver *webrtc.RTPTransceiver Sender *webrtc.RTPSender } type CallEvent struct { EventId string `json:"eventId"` From string `json:"from"` Data []byte `json:"data"` Payload map[string]interface{} `json:"payload"` } type RemoteTrack struct { ID string Track *webrtc.TrackLocalStaticRTP rdv *int32 } type OnICECandidateFunc func(string, *webrtc.ICECandidate) error func NewWebRTCCallManager(id string, token string, eventHandlers ...WebrtcCallEventManager) (webRTCCallManager *WebRTCCallManager, err error) { squadList, err := loadHostedSquads(token, id) if err != nil { return } squads := make(map[string]*Squad) for _, squad := range squadList { squads[squad.ID] = squad } logger.Println(squads) webRTCCallManager = &WebRTCCallManager{ middlewares: eventHandlers, ID: id, AudioTransceiver: make(map[string][]*PeerSender), VideoTransceiver: make(map[string][]*PeerSender), DataChannels: make(map[string]*DataChannel), PendingCandidates: make(map[string][]*webrtc.ICECandidate), LocalSD: make(map[string]*webrtc.SessionDescription), RTCPeerConnections: make(map[string]*RTCPeerConnection), RemoteTracks: make(map[string][]*RemoteTrack), Squads: squads, SquadMapMux: &sync.RWMutex{}, RTCPeerConnectionMapMux: &sync.RWMutex{}, LocalSDMapMux: &sync.RWMutex{}, CandidateMux: &sync.RWMutex{}, DataChannelMapMux: &sync.RWMutex{}, AudioSenderMux: &sync.RWMutex{}, VideoSenderMux: &sync.RWMutex{}, RemoteTracksMux: &sync.RWMutex{}, } return } func loadHostedSquads(token string, hostId string) (squads []*Squad, err error) { em := NewEncryptionManager() sig := em.SignRequestHMAC(hostId) body, err := json.Marshal(map[string]interface{}{ "type": LIST_HOSTED_SQUADS_BY_HOST, "mac": sig, "from": hostId, "peerType":"node", "payload": map[string]string{ "host": hostId, "lastIndex": "0", }, }) if err != nil { return } res, err := http.Post("https://app.zippytal.com/req", "application/json", bytes.NewBuffer(body)) if err != nil { logger.Println("error come from there in webrtc call manager") return } bs, err := io.ReadAll(res.Body) if err != nil { return } var payload map[string]any if err = json.Unmarshal(bs, &payload); err != nil { return } b, err := json.Marshal(payload["squads"]) if err != nil { return } err = json.Unmarshal(b, &squads) return } func (wm *WebRTCCallManager) sendSignalingMessage(messageType, from, to string, payload map[string]interface{}) (err error) { bs, err := json.Marshal(payload) if err != nil { return } err = wm.stream.Send(&SignalingMessage{ Type: messageType, From: from, To: to, Payload: bs, }) return } func (wm *WebRTCCallManager) CreateOffer(ctx context.Context, target string, from string, cb OnICECandidateFunc) (err error) { peerConnection, err := wm.createPeerConnection(target, from, "aa5da62f-2800-4dd5-bf86-423cda048120", webrtc.SDPTypeOffer, cb) if err != nil { return } logger.Println("connection created") rawOffer, err := peerConnection.CreateOffer(nil) if err != nil { return } if err = peerConnection.SetLocalDescription(rawOffer); err != nil { return } wm.RTCPeerConnectionMapMux.Lock() logger.Println("adding for target", target) wm.RTCPeerConnections[target] = &RTCPeerConnection{ PeerConnection: peerConnection, makingOffer: true, makingOfferLock: &sync.Mutex{}, negotiate: wm.negotiate, } wm.RTCPeerConnectionMapMux.Unlock() err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_OFFER), wm.ID, target, map[string]interface{}{ "to": target, "from": wm.ID, "sdp": rawOffer.SDP, }) return } func (wm *WebRTCCallManager) HandleOffer(ctx context.Context, from, to string, req map[string]string, cb OnICECandidateFunc) (err error) { done, errCh := make(chan struct{}), make(chan error) go func() { if _, ok := wm.Squads[req[SQUAD_ID]]; !ok { err = fmt.Errorf("no corresponding squad") errCh <- err return } peerConnection, err := wm.createPeerConnection(from, to, req[SQUAD_ID], webrtc.SDPTypeAnswer, cb) if err != nil { errCh <- err return } wm.RTCPeerConnectionMapMux.Lock() wm.RTCPeerConnections[from] = &RTCPeerConnection{ PeerConnection: peerConnection, makingOffer: false, makingOfferLock: &sync.Mutex{}, negotiate: wm.negotiate, } wm.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 } wm.LocalSDMapMux.Lock() wm.LocalSD[from] = &rawAnswer wm.LocalSDMapMux.Unlock() if err = peerConnection.SetLocalDescription(rawAnswer); err != nil { errCh <- err return } wm.SquadMapMux.Lock() wm.Squads[req[SQUAD_ID]].Members = append(wm.Squads[req[SQUAD_ID]].Members, from) wm.SquadMapMux.Unlock() if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_ANSWER), wm.ID, from, map[string]interface{}{ "to": from, "from": wm.ID, "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 (wm *WebRTCCallManager) HandleAnswer(ctx context.Context, from, to string, req map[string]string) (err error) { wm.RTCPeerConnectionMapMux.Lock() defer wm.RTCPeerConnectionMapMux.Unlock() defer func() { if r := recover(); err != nil { logger.Printf("recover from panic in handle answer : %v\n", r) } }() if _, ok := wm.RTCPeerConnections[from]; !ok { err = fmt.Errorf("no corresponding peer connection for id : %s", from) return } peerConnnection := wm.RTCPeerConnections[from] logger.Println("---------------------") logger.Println(req[SDP]) logger.Println("---------------------") if err = peerConnnection.SetRemoteDescription(webrtc.SessionDescription{ Type: webrtc.SDPTypeAnswer, SDP: req[SDP], }); err != nil { logger.Println("error occured while setting remote description in handle answer") return } if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_COUNTER_OFFER), wm.ID, from, map[string]interface{}{ "from": wm.ID, "to": from, }); err != nil { return } wm.CandidateMux.Lock() for _, candidate := range wm.PendingCandidates[from] { logger.Println("sending candidate from answer to", from) if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_CANDIDATE), wm.ID, from, map[string]interface{}{ "from": wm.ID, "to": from, "candidate": candidate.ToJSON().Candidate, "sdpMid": *candidate.ToJSON().SDPMid, "sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), }); err != nil { wm.CandidateMux.Unlock() return } } wm.CandidateMux.Unlock() wm.CandidateMux.Lock() delete(wm.PendingCandidates, from) wm.CandidateMux.Unlock() wm.LocalSDMapMux.Lock() delete(wm.LocalSD, from) wm.LocalSDMapMux.Unlock() return } func (wm *WebRTCCallManager) HandleCounterOffer(ctx context.Context, from, to string, req map[string]string) (err error) { wm.RTCPeerConnectionMapMux.Lock() if _, ok := wm.RTCPeerConnections[from]; !ok { err = fmt.Errorf("no field corresponding peer connection for id %s", from) wm.RTCPeerConnectionMapMux.Unlock() return } logger.Println("handling counter offer") //connection := wm.RTCPeerConnections[from] wm.RTCPeerConnectionMapMux.Unlock() // wm.LocalSDMapMux.Lock() // if err = connection.SetLocalDescription(*wm.LocalSD[from]); err != nil { // wm.LocalSDMapMux.Unlock() // return // } // wm.LocalSDMapMux.Unlock() wm.CandidateMux.Lock() for _, candidate := range wm.PendingCandidates[from] { logger.Println("sending candidate to", from) if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_CANDIDATE), wm.ID, from, map[string]interface{}{ "from": wm.ID, "to": from, "candidate": candidate.ToJSON().Candidate, "sdpMid": *candidate.ToJSON().SDPMid, "sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), }); err != nil { wm.CandidateMux.Unlock() return } } delete(wm.PendingCandidates, from) wm.CandidateMux.Unlock() wm.LocalSDMapMux.Lock() delete(wm.LocalSD, from) wm.LocalSDMapMux.Unlock() return } func (wm *WebRTCCallManager) createPeerConnection(target string, from string, squadId 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) } }() config := webrtc.Configuration{ ICEServers: []webrtc.ICEServer{ { URLs: []string{"stun:stun.l.google.com:19302", "stun:stunserver.org:3478"}, }, }, SDPSemantics: webrtc.SDPSemanticsUnifiedPlanWithFallback, } peerConnection, err = webrtc.NewPeerConnection(config) if err != nil { return } logger.Println("---------------------------------------------------") if peerType == webrtc.SDPTypeAnswer { 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) { var event CallEvent if err := json.Unmarshal(msg.Data, &event); err != nil { logger.Println(err) return } for _, handler := range wm.middlewares { if err := handler.HandleCallEvent(event.From, squadId, event.EventId, event.Payload, event.Data, wm); err != nil { logger.Println(err) continue } } }) logger.Println("new channel for target : ", target) channel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold) channel.OnBufferedAmountLow(func() { }) wm.DataChannelMapMux.Lock() logger.Println(target) l := int32(0) wm.DataChannels[target] = &DataChannel{ DataChannel: channel, l: &l, } wm.DataChannelMapMux.Unlock() } else { peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) { wm.DataChannelMapMux.Lock() l := int32(0) wm.DataChannels[target] = &DataChannel{ DataChannel: dc, l: &l, } wm.DataChannelMapMux.Unlock() dc.OnOpen(func() { logger.Printf("got a new open datachannel %s\n", dc.Label()) }) dc.OnMessage(func(msg webrtc.DataChannelMessage) { var event CallEvent if err := json.Unmarshal(msg.Data, &event); err != nil { logger.Println(err) return } for _, handler := range wm.middlewares { if err := handler.HandleCallEvent(event.From, squadId, event.EventId, event.Payload, event.Data, wm); err != nil { logger.Println(err) continue } } }) }) } wm.RemoteTracksMux.RLock() for _, id := range wm.Squads[squadId].Members { if id != target { <-time.NewTimer(time.Millisecond * 50).C if _, ok := wm.RemoteTracks[id]; !ok { continue } for _, track := range wm.RemoteTracks[id] { transceiver, err := peerConnection.AddTransceiverFromKind(track.Track.Kind(), webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}) if err != nil { logger.Println("add track error") continue } if err := transceiver.Sender().ReplaceTrack(track.Track); err != nil { logger.Println("add track error") continue } if track.Track.Kind() == webrtc.RTPCodecTypeVideo { wm.VideoSenderMux.Lock() if len(wm.VideoTransceiver) == 0 { wm.VideoTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}} } else { wm.VideoTransceiver[id] = append(wm.VideoTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver}) } wm.VideoSenderMux.Unlock() } else if track.Track.Kind() == webrtc.RTPCodecTypeAudio { wm.AudioSenderMux.Lock() if len(wm.AudioTransceiver) == 0 { wm.AudioTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}} } else { wm.AudioTransceiver[id] = append(wm.AudioTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver}) } wm.AudioSenderMux.Unlock() } logger.Println("track added", track) } } } wm.RemoteTracksMux.RUnlock() peerConnection.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) { if pcs == webrtc.PeerConnectionStateClosed || pcs == webrtc.PeerConnectionStateDisconnected || pcs == webrtc.PeerConnectionStateFailed { logger.Println(pcs) wm.HandleLeavingMember(target, squadId) } }) 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.OnTrack(func(tr *webrtc.TrackRemote, r *webrtc.RTPReceiver) { logger.Println("got new track") defer func() { if stopErr := r.Stop(); stopErr != nil { logger.Println(stopErr) } }() go func() { ticker := time.NewTicker(1500 * time.Millisecond) for range ticker.C { if rtcpSendErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(tr.SSRC())}}); rtcpSendErr != nil { logger.Println(rtcpSendErr) break } } }() uniqId := uuid.New() i := fmt.Sprintf("%s/%s", target, uniqId.String()) localTrack, newTrackErr := webrtc.NewTrackLocalStaticRTP(tr.Codec().RTPCodecCapability, i, i) if newTrackErr != nil { return } logger.Println(localTrack) rtpbuf := make([]byte, 1400) flag := int32(0) remote := &RemoteTrack{ID: target, Track: localTrack, rdv: &flag} wm.RemoteTracksMux.Lock() if len(wm.RemoteTracks[target]) == 0 { wm.RemoteTracks[target] = []*RemoteTrack{remote} } else { wm.RemoteTracks[target] = append(wm.RemoteTracks[target], remote) } index := len(wm.RemoteTracks[target]) logger.Println(index, wm.RemoteTracks) wm.RemoteTracksMux.Unlock() wm.SquadMapMux.RLock() for _, id := range wm.Squads[squadId].Members { if id != target { if _, ok := wm.RTCPeerConnections[id]; !ok { continue } connection := wm.RTCPeerConnections[id] transceiver, tranceiverErr := connection.AddTransceiverFromKind(localTrack.Kind(), webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}) if tranceiverErr != nil { logger.Println(tranceiverErr) continue } if replaceTrackErr := transceiver.Sender().ReplaceTrack(localTrack); replaceTrackErr != nil { logger.Println(replaceTrackErr) continue } go func() { rtcpBuf := make([]byte, 1500) for { if _, _, rtcpErr := transceiver.Sender().Read(rtcpBuf); rtcpErr != nil { return } } }() if localTrack.Kind() == webrtc.RTPCodecTypeAudio { wm.AudioSenderMux.Lock() if len(wm.AudioTransceiver) == 0 { wm.AudioTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}} } else { wm.AudioTransceiver[target] = append(wm.AudioTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver}) } wm.AudioSenderMux.Unlock() } else if localTrack.Kind() == webrtc.RTPCodecTypeVideo { wm.VideoSenderMux.Lock() if len(wm.VideoTransceiver) == 0 { wm.VideoTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}} } else { wm.VideoTransceiver[target] = append(wm.VideoTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver}) } wm.VideoSenderMux.Unlock() } go func() { <-time.NewTimer(time.Second).C connection.negotiate(id, squadId) }() } } wm.SquadMapMux.RUnlock() for { i, _, readErr := tr.Read(rtpbuf) if readErr != nil { logger.Println(readErr) break } f := atomic.LoadInt32(remote.rdv) if f == 0 { if _, writeErr := localTrack.Write(rtpbuf[:i]); writeErr != nil && !errors.Is(writeErr, io.ErrClosedPipe) { logger.Println(writeErr) break } else { _ = rtpbuf[:i] } } } }) peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) { if i == nil { return } wm.CandidateMux.Lock() defer wm.CandidateMux.Unlock() desc := peerConnection.RemoteDescription() if desc == nil { logger.Println("generated candidate appended to list : ", i) wm.PendingCandidates[target] = append(wm.PendingCandidates[target], i) } else { logger.Println("generated candidate : ", i) if iceCandidateErr := cb(target, i); iceCandidateErr != nil { logger.Println(iceCandidateErr) } } }) return } func (wm *WebRTCCallManager) HandleRennegotiationOffer(from string, sdp string) (err error) { wm.RTCPeerConnectionMapMux.Lock() defer wm.RTCPeerConnectionMapMux.Unlock() if _, ok := wm.RTCPeerConnections[from]; !ok { err = fmt.Errorf("no corresponding peer connection for id %s", from) return } wm.RTCPeerConnections[from].makingOfferLock.Lock() if wm.RTCPeerConnections[from].makingOffer { wm.RTCPeerConnections[from].makingOfferLock.Unlock() return fmt.Errorf("already making an offer or state is stable") } wm.RTCPeerConnections[from].makingOfferLock.Unlock() if err = wm.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil { return } localSd, err := wm.RTCPeerConnections[from].CreateAnswer(nil) if err != nil { return } if err = wm.RTCPeerConnections[from].SetLocalDescription(localSd); err != nil { return } if err = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_ANSWER), wm.ID, from, map[string]interface{}{ "to": from, "sdp": localSd.SDP, }); err != nil { logger.Println(err) return } return } func (wm *WebRTCCallManager) HandleRennegotiationAnswer(from string, sdp string) (err error) { wm.RTCPeerConnectionMapMux.Lock() defer wm.RTCPeerConnectionMapMux.Unlock() wm.RTCPeerConnections[from].makingOfferLock.Lock() if wm.RTCPeerConnections[from].makingOffer { wm.RTCPeerConnections[from].makingOfferLock.Unlock() return fmt.Errorf("already making an offer or state is stable") } wm.RTCPeerConnections[from].makingOfferLock.Unlock() if _, ok := wm.RTCPeerConnections[from]; !ok { err = fmt.Errorf("no corresponding peer connection for id %s", from) return } err = wm.RTCPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer}) return } func (wm *WebRTCCallManager) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) { wm.RTCPeerConnectionMapMux.Lock() defer wm.RTCPeerConnectionMapMux.Unlock() if candidate != nil { err = wm.RTCPeerConnections[from].AddICECandidate(*candidate) } return } func (wm *WebRTCCallManager) HandleLeavingMember(id string, squadId string) { wm.RTCPeerConnectionMapMux.Lock() if _, ok := wm.RTCPeerConnections[id]; !ok { wm.RTCPeerConnectionMapMux.Unlock() return } wm.RTCPeerConnectionMapMux.Unlock() defer func() { wm.RTCPeerConnectionMapMux.Lock() if _, ok := wm.RTCPeerConnections[id]; ok { if closeErr := wm.RTCPeerConnections[id].Close(); closeErr != nil { logger.Println("peer connection close error", closeErr) } } delete(wm.RTCPeerConnections, id) wm.RTCPeerConnectionMapMux.Unlock() }() logger.Printf("peer %s is leaving the squad\n", id) wm.DataChannelMapMux.Lock() if _, ok := wm.DataChannels[id]; ok { wm.DataChannels[id].DataChannel.Close() } delete(wm.DataChannels, id) wm.DataChannelMapMux.Unlock() wm.LocalSDMapMux.Lock() delete(wm.LocalSD, id) wm.LocalSDMapMux.Unlock() delete(wm.PendingCandidates, id) wm.RemoteTracksMux.Lock() delete(wm.RemoteTracks, id) wm.RemoteTracksMux.Unlock() wm.AudioSenderMux.Lock() for peerId, peerSender := range wm.AudioTransceiver { if peerId != id { logger.Println("senders", peerSender) c := 0 for i, sender := range peerSender { if sender.ID == id { if senderErr := sender.Transceiver.Sender().Stop(); senderErr != nil { logger.Println(senderErr) } if transceiverErr := sender.Transceiver.Stop(); transceiverErr != nil { logger.Println("transceiverErr occured with video", transceiverErr) } peerSender[len(peerSender)-i-1], peerSender[i] = peerSender[i], peerSender[len(peerSender)-i-1] c++ } } wm.AudioTransceiver[peerId] = wm.AudioTransceiver[peerId][:len(peerSender)-(c)] logger.Println(wm.AudioTransceiver[peerId]) } } for _, transceiver := range wm.AudioTransceiver[id] { if senderErr := transceiver.Transceiver.Sender().Stop(); senderErr != nil { logger.Println(senderErr) } if stopErr := transceiver.Transceiver.Stop(); stopErr != nil { logger.Println("transceiver audio stop error", stopErr) } } delete(wm.AudioTransceiver, id) wm.AudioSenderMux.Unlock() wm.VideoSenderMux.Lock() for peerId, peerSender := range wm.VideoTransceiver { if peerId != id { c := 0 logger.Println("senders", peerSender) for i, sender := range peerSender { if sender.ID == id { if senderErr := sender.Transceiver.Sender().Stop(); senderErr != nil { logger.Println(senderErr) } if transceiverErr := sender.Transceiver.Stop(); transceiverErr != nil { logger.Println("transceiverErr occured with video", transceiverErr) } peerSender[len(peerSender)-i-1], peerSender[i] = peerSender[i], peerSender[len(peerSender)-i-1] c++ } } wm.VideoTransceiver[peerId] = wm.VideoTransceiver[peerId][:len(peerSender)-(c)] logger.Println(wm.VideoTransceiver[peerId]) } } for _, transceiver := range wm.VideoTransceiver[id] { if senderErr := transceiver.Transceiver.Sender().Stop(); senderErr != nil { logger.Println(senderErr) } if stopErr := transceiver.Transceiver.Stop(); stopErr != nil { logger.Println("transceiver video stop error", stopErr) } } delete(wm.VideoTransceiver, id) wm.VideoSenderMux.Unlock() if _, ok := wm.Squads[squadId]; ok { wm.SquadMapMux.Lock() var index int for i, v := range wm.Squads[squadId].Members { if v == id { index = i break } } if len(wm.Squads[squadId].Members) < 2 { wm.Squads[squadId].Members = []string{} } else { wm.Squads[squadId].Members = append(wm.Squads[squadId].Members[:index], wm.Squads[squadId].Members[index+1:]...) } wm.SquadMapMux.Unlock() } } func (wm *WebRTCCallManager) negotiate(target string, squadId string) { wm.RTCPeerConnectionMapMux.Lock() defer wm.RTCPeerConnectionMapMux.Unlock() if _, ok := wm.RTCPeerConnections[target]; !ok { return } wm.RTCPeerConnections[target].makingOfferLock.Lock() wm.RTCPeerConnections[target].makingOffer = true wm.RTCPeerConnections[target].makingOfferLock.Unlock() defer func() { wm.RTCPeerConnections[target].makingOfferLock.Lock() wm.RTCPeerConnections[target].makingOffer = false wm.RTCPeerConnections[target].makingOfferLock.Unlock() }() wm.SquadMapMux.RLock() defer wm.SquadMapMux.RUnlock() for _, id := range wm.Squads[squadId].Members { if _, ok := wm.RTCPeerConnections[id]; !ok { continue } connection := wm.RTCPeerConnections[id] if connection.SignalingState() == webrtc.SignalingStateStable { 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 = wm.sendSignalingMessage(string(HOSTED_SQUAD_WEBRTC_RENNEGOTIATION_OFFER), wm.ID, id, map[string]interface{}{ "to": id, "sdp": localSd.SDP, }); err != nil { logger.Println(err) return } } } }