package localserver import ( "context" "encoding/json" "fmt" "os" "path/filepath" "strconv" "strings" "github.com/pion/webrtc/v3" ) type SquadVideoChannelsHandler struct { SquadId string SquadName string HostId string SquadMembersId []string DataChannels map[string]*DataChannel VideoChanDataChannels map[string]*DataChannel VideoChanDataChannelsFlag *uint32 DataChannelsFlag *uint32 VideoChannelsFlag *uint32 VideoChannel *VideoChannel reqChans []chan<- *SquadRequest } func NewSquadVideoChannelsHandler(hostId string, squadId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, dataChannelFlag *uint32) (squadVideoChannelsHandler *SquadVideoChannelsHandler, err error) { _, err = os.ReadDir(filepath.Join(dataPath, "data", "squads", squadId, "videoChannel")) if err != nil { if os.IsNotExist(err) { // logger.Printf("creating videoChannels directory for squad %s...\n", squadId) mkdirErr := os.MkdirAll(filepath.Join(dataPath, "data", "squads", squadId, "videoChannel"), 0700) if mkdirErr != nil { return nil, mkdirErr } file, ferr := os.Create(filepath.Join(dataPath, "data", "squads", squadId, "videoChannel", "videoChannelConfig.json")) defer func() { _ = file.Close() }() if ferr != nil { return nil, ferr } baseConfig := VideoChannelConfig{ ID: MEETING, Owner: owner, ChannelType: "public", CurrentMembersId: make([]string, 0), Members: make([]string, 0), } bs, jsonErr := json.Marshal(baseConfig) if jsonErr != nil { return nil, jsonErr } if _, writeErr := file.WriteString(string(bs)); writeErr != nil { return nil, writeErr } _, err = os.ReadDir(filepath.Join(dataPath, "data", "squads", squadId, "videoChannel")) if err != nil { return nil, err } } else { return } } var bs []byte bs, err = os.ReadFile(filepath.Join(dataPath, "data", "squads", squadId, "videoChannel", "videoChannelConfig.json")) if err != nil { return nil, err } // logger.Println(string(bs)) var vcc VideoChannelConfig if err = json.Unmarshal(bs, &vcc); err != nil { return nil, err } // logger.Println("videoChannels data :", vcc.ID, vcc.ChannelType, vcc.Owner, vcc.Members) vc := NewVideoChannel(vcc.ID, vcc.Owner, vcc.ChannelType, vcc.Members, make([]string, 0), make(map[string]*VideoChannelMember)) videoChannelFlag := uint32(0) videoChanDCFlag := uint32(0) squadVideoChannelsHandler = &SquadVideoChannelsHandler{ SquadId: squadId, HostId: hostId, SquadMembersId: authorizedMembers, DataChannels: dataChannels, VideoChanDataChannels: make(map[string]*DataChannel), VideoChanDataChannelsFlag: &videoChanDCFlag, DataChannelsFlag: dataChannelFlag, VideoChannel: vc, VideoChannelsFlag: &videoChannelFlag, } return } func (zvch *SquadVideoChannelsHandler) sendDataChannelMessage(reqType string, from string, to string, payload map[string]interface{}) (<-chan struct{}, <-chan error) { done, errCh := make(chan struct{}), make(chan error) go func() { if err := atomicallyExecute(zvch.VideoChanDataChannelsFlag, func() (err error) { if _, ok := zvch.VideoChanDataChannels[to]; ok { bs, jsonErr := json.Marshal(&SquadResponse{ Type: reqType, From: from, To: to, Payload: payload, }) if jsonErr != nil { return jsonErr } err = zvch.VideoChanDataChannels[to].DataChannel.SendText(string(bs)) } return }); err != nil { errCh <- err return } done <- struct{}{} }() return done, errCh } func (zvch *SquadVideoChannelsHandler) Init(ctx context.Context, authorizedMembers []string) (err error) { return } func (zvch *SquadVideoChannelsHandler) Subscribe(ctx context.Context, publisher <-chan *SquadRequest) (reqChan chan *SquadRequest, done chan struct{}, errCh chan error) { reqChan, done, errCh = make(chan *SquadRequest), make(chan struct{}), make(chan error) zvch.reqChans = append(zvch.reqChans, reqChan) go func() { for { select { case <-ctx.Done(): done <- struct{}{} return case req := <-publisher: if err := zvch.handleSquadRequest(ctx, req); err != nil { errCh <- err } } } }() return } func (zvch *SquadVideoChannelsHandler) signalCandidate(from string, to string, candidate *webrtc.ICECandidate) (err error) { d, e := zvch.sendDataChannelMessage(string(VIDEO_CHANNEL_WEBRTC_CANDIDATE), from, to, map[string]interface{}{ "from": from, "to": to, "candidate": candidate.ToJSON().Candidate, "sdpMid": *candidate.ToJSON().SDPMid, "sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)), }) select { case <-d: case err = <-e: } return } func (zvch *SquadVideoChannelsHandler) JoinVideoChannel(channelId string, userId string, sdp string) (err error) { err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { videoChannel := zvch.VideoChannel videoChannel.CurrentMembersId = append(videoChannel.CurrentMembersId, userId) videoChannel.CurrentMembers[userId] = &VideoChannelMember{} signalMembers := func(members []string) { for _, u := range members { done, e := zvch.sendDataChannelMessage(USER_JOINED_VIDEO_CHANNEL, "node", u, map[string]interface{}{ "userId": userId, "channelId": channelId, }) select { case <-done: case <-e: // logger.Println(err) } } } signalMembers(zvch.SquadMembersId) d, e := videoChannel.HandleOffer(context.Background(), channelId, userId, sdp, zvch.HostId, zvch.sendDataChannelMessage, zvch.signalCandidate) select { case <-d: case err = <-e: } return }) return } func (zvch *SquadVideoChannelsHandler) LeaveVideoChannel(channelId string, userId string) (err error) { err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { videoChannel := zvch.VideoChannel var index int var contain bool for i, v := range videoChannel.CurrentMembersId { if v == userId { index = i contain = true } } if !contain { err = fmt.Errorf("this channel does not contain the provided user Id") return } defer videoChannel.HandleLeavingMember(userId) if len(videoChannel.CurrentMembersId) <= 1 { videoChannel.CurrentMembersId = make([]string, 0) } else { videoChannel.CurrentMembersId = append(videoChannel.CurrentMembersId[:index], videoChannel.CurrentMembersId[index+1:]...) } delete(videoChannel.CurrentMembers, userId) signalMembers := func(members []string) { for _, u := range members { done, e := zvch.sendDataChannelMessage(USER_LEFT_VIDEO_CHANNEL, "node", u, map[string]interface{}{ "userId": userId, "channelId": channelId, }) select { case <-done: case <-e: // logger.Println(err) } } } signalMembers(zvch.SquadMembersId) return }) return } func (zvch *SquadVideoChannelsHandler) handleDataChannel(ctx context.Context, dc *DataChannel) (catched bool) { var label string _ = atomicallyExecute(dc.l, func() (err error) { label = dc.DataChannel.Label() return }) if strings.Contains(label, "video_channel_data") { command := strings.Split(label, "|") catched = true _ = atomicallyExecute(zvch.VideoChanDataChannelsFlag, func() (err error) { zvch.VideoChanDataChannels[command[1]] = dc return }) dc.DataChannel.OnOpen(func() { fmt.Println("datachann in squad video chann fking created") bs, err := json.Marshal(map[string]any{ "type": "init_video_channel", "from": NodeID, "to": command[1], "payload": map[string]any{ "videoChannel": zvch.VideoChannel, }, }) if err != nil { fmt.Println(err) } _ = dc.DataChannel.SendText(string(bs)) }) dc.DataChannel.OnClose(func() { fmt.Println("closing gratefully video channel dc...") _ = atomicallyExecute(zvch.VideoChanDataChannelsFlag, func() (err error) { delete(zvch.VideoChanDataChannels, command[1]) fmt.Println("dc closed gracefully...") return }) }) dc.DataChannel.OnError(func(err error) { fmt.Println("error in video channel dc...") _ = atomicallyExecute(zvch.VideoChanDataChannelsFlag, func() (err error) { delete(zvch.VideoChanDataChannels, command[1]) fmt.Println("dc closed on error...") return }) }) } return } func (zvch *SquadVideoChannelsHandler) handleSquadRequest(ctx context.Context, req *SquadRequest) (err error) { switch req.ReqType { case LEAVE_ZONE: // logger.Println("*-----------------handling leaving squad---------------*") if err = VerifyFieldsString(req.Payload, "userId"); err != nil { return } vc := zvch.VideoChannel var contain bool var id string for _, member := range vc.CurrentMembersId { if member == req.Payload["userId"].(string) { id = member contain = true // logger.Printf("*------------------id is %s--------------*\n", id) break } } if contain { err = zvch.LeaveVideoChannel(vc.ID, id) break } case string(REMOVED_SQUAD_AUTHORIZED_MEMBER): if err = VerifyFieldsString(req.Payload, "userId"); err != nil { return } var index int var found bool for i, m := range zvch.SquadMembersId { if m == req.Payload["userId"].(string) { index = i found = true break } } if !found { err = fmt.Errorf("no such member in squad") return } zvch.SquadMembersId = append(zvch.SquadMembersId[:index], zvch.SquadMembersId[index+1:]...) case string(NEW_AUTHORIZED_SQUAD_MEMBER): if err = VerifyFieldsString(req.Payload, "userId"); err != nil { return } var contain bool for _, m := range zvch.SquadMembersId { if m == req.Payload["userId"].(string) { contain = true break } } if !contain { zvch.SquadMembersId = append(zvch.SquadMembersId, req.Payload["userId"].(string)) } case JOIN_VIDEO_CHANNEL: fmt.Println("wuwuuwuwwuuwuw i got this") fmt.Println(req.Payload) if err = VerifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { return } err = zvch.JoinVideoChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string), req.Payload["sdp"].(string)) case LEAVE_VIDEO_CHANNEL: if err = VerifyFieldsString(req.Payload, "channelId", "userId"); err != nil { return } err = zvch.LeaveVideoChannel(req.Payload["channelId"].(string), req.Payload["userId"].(string)) case string(VIDEO_CHANNEL_WEBRTC_COUNTER_OFFER): // logger.Println("handling video channel counter offer") if err = VerifyFieldsString(req.Payload, "channelId", "userId"); err != nil { return } err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { err = zvch.VideoChannel.HandleCounterOffer(ctx, req.Payload["userId"].(string), zvch.sendDataChannelMessage) return }) case string(VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER): if err = VerifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { return } err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { err = zvch.VideoChannel.HandleRennegotiationOffer(req.Payload["userId"].(string), req.Payload["sdp"].(string), zvch.sendDataChannelMessage) return }) case string(VIDEO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER): if err = VerifyFieldsString(req.Payload, "channelId", "userId", "sdp"); err != nil { return } err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { err = zvch.VideoChannel.HandleRennegotiationAnswer(req.Payload["userId"].(string), req.Payload["sdp"].(string)) return }) case string(VIDEO_CHANNEL_WEBRTC_CANDIDATE): // logger.Println("handling video channel webrtc candidate") // logger.Println(req.Payload) if err = VerifyFieldsString(req.Payload, FROM, "candidate", "sdpMLineIndex", "sdpMid", "channelId", "userId"); err != nil { return } // logger.Println(req.Payload) i, convErr := strconv.Atoi(req.Payload["sdpMLineIndex"].(string)) if convErr != nil { return convErr } SDPMLineIndex := uint16(i) sdpMid := req.Payload["sdpMid"].(string) // logger.Println(sdpMid, SDPMLineIndex) err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { fmt.Println("uwuwuw adding candidate", req.Payload["candidate"].(string)) err = zvch.VideoChannel.AddCandidate(&webrtc.ICECandidateInit{ Candidate: req.Payload["candidate"].(string), SDPMid: &sdpMid, SDPMLineIndex: &SDPMLineIndex, }, req.Payload["userId"].(string)) return }) default: // logger.Println("video channel handler still in process of implementation") } return }