package localserver import ( "context" "encoding/json" "fmt" "io/fs" "os" "path/filepath" "strconv" "strings" "github.com/pion/webrtc/v3" ) const ( JOIN_VIDEO_CHANNEL = "join_video_channel" LEAVE_VIDEO_CHANNEL = "leave_video_channel" LIST_VIDEO_CHANNELS = "list_video_channels" GET_VIDEO_CHANNELS = "get_video_channels" GET_VIDEO_CHANNEL = "get_video_channel" CREATE_VIDEO_CHANNEL = "create_video_channel" DELETE_VIDEO_CHANNEL = "delete_video_channel" EDIT_VIDEO_CHANNEL_TYPE = "edit_video_channel_type" EDIT_VIDEO_CHANNEL_NAME = "edit_video_channel_name" ADD_VIDEO_CHANNEL_MEMBERS = "add_video_channel_members" REMOVE_VIDEO_CHANNEL_MEMBER = "remove_video_channel_member" ) const ( USER_JOINED_VIDEO_CHANNEL = "user_joined_video_channel" USER_LEFT_VIDEO_CHANNEL = "user_left_video_channel" VIDEO_CHANNNEL_NAME_EDITED = "video_channel_name_edited" VIDEO_CHANNNEL_TYPE_EDITED = "video_channel_type_edited" VIDEO_CHANNEL_MEMBER_REMOVED = "video_channel_member_removed" VIDEO_CHANNEL_MEMBER_ADDED = "video_channel_member_added" ADDED_IN_VIDEO_CHANNEL = "added_in_video_channel" REMOVED_FROM_VIDEO_CHANNEL = "removed_from_video_channel" ) type VideoChannelMember struct { } type VideoChannelConfig struct { ID string `json:"id"` Owner string `json:"owner"` ChannelType string `json:"channelType"` CurrentMembersId []string Members []string `json:"members"` } type ZoneVideoChannelsHandler struct { ZoneId string ZoneName string HostId string ZoneMembersId []string DataChannels map[string]*DataChannel VideoChanDataChannels map[string]*DataChannel VideoChanDataChannelsFlag *uint32 DataChannelsFlag *uint32 VideoChannelsFlag *uint32 VideoChannels map[string]*VideoChannel reqChans []chan<- *ZoneRequest } const MEETING string = "meeting" func NewZoneVideoChannelsHandler(hostId string, zoneId string, owner string, authorizedMembers []string, dataChannels map[string]*DataChannel, dataChannelFlag *uint32) (zoneVideoChannelsHandler *ZoneVideoChannelsHandler, err error) { var dirs []fs.DirEntry dirs, err = os.ReadDir(filepath.Join(dataPath, "data", "zones", zoneId, "videoChannels")) if err != nil { if os.IsNotExist(err) { logger.Printf("creating videoChannels directory for zone %s...\n", zoneId) mkdirErr := os.MkdirAll(filepath.Join(dataPath, "data", "zones", zoneId, "videoChannels", MEETING), 0700) if mkdirErr != nil { return nil, mkdirErr } file, ferr := os.Create(filepath.Join(dataPath, "data", "zones", zoneId, "videoChannels", MEETING, "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 } dirs, err = os.ReadDir(filepath.Join(dataPath, "data", "zones", zoneId, "videoChannels")) if err != nil { return nil, err } } else { return } } videoChannels := make(map[string]*VideoChannel) for _, videoChannel := range dirs { if strings.HasPrefix(videoChannel.Name(), ".") { continue } var bs []byte bs, err = os.ReadFile(filepath.Join(dataPath, "data", "zones", zoneId, "videoChannels", videoChannel.Name(), "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)) videoChannels[vc.ID] = vc } videoChannelFlag := uint32(0) videoChanDCFlag := uint32(0) zoneVideoChannelsHandler = &ZoneVideoChannelsHandler{ ZoneId: zoneId, HostId: hostId, ZoneMembersId: authorizedMembers, DataChannels: dataChannels, VideoChanDataChannels: make(map[string]*DataChannel), VideoChanDataChannelsFlag: &videoChanDCFlag, DataChannelsFlag: dataChannelFlag, VideoChannels: videoChannels, VideoChannelsFlag: &videoChannelFlag, } return } func (zvch *ZoneVideoChannelsHandler) sendZoneRequest(reqType string, from string, payload map[string]interface{}) { go func() { for _, rc := range zvch.reqChans { rc <- &ZoneRequest{ ReqType: reqType, From: from, Payload: payload, } } }() } func (zvch *ZoneVideoChannelsHandler) 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(&ZoneResponse{ 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 *ZoneVideoChannelsHandler) Init(ctx context.Context, authorizedMembers []string) (err error) { for _, member := range authorizedMembers { if serr := zvch.SetAllPublicVideoChannelForUser(member); serr != nil { logger.Println(serr) } } return } func (zvch *ZoneVideoChannelsHandler) Subscribe(ctx context.Context, publisher <-chan *ZoneRequest) (reqChan chan *ZoneRequest, done chan struct{}, errCh chan error) { reqChan, done, errCh = make(chan *ZoneRequest), 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.handleZoneRequest(ctx, req); err != nil { errCh <- err } } } }() return } func (zvch *ZoneVideoChannelsHandler) 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 *ZoneVideoChannelsHandler) GetVideoChannels(userId string, channelsId ...interface{}) (err error) { videoChannels := make([]*VideoChannel, 0, len(channelsId)) for _, id := range channelsId { if _, ok := id.(string); !ok { err = fmt.Errorf("id of wrong type") return } logger.Println("videoChannel from get videoChannels", id.(string)) _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { if _, ok := zvch.VideoChannels[id.(string)]; ok { logger.Println(zvch.VideoChannels[id.(string)]) videoChannels = append(videoChannels, zvch.VideoChannels[id.(string)]) } return }) } done, e := zvch.sendDataChannelMessage("get_video_channels_response", "node", userId, map[string]interface{}{ "videoChannels": videoChannels, }) select { case <-done: fmt.Println("done") fmt.Println(videoChannels) case err = <-e: } return } func (zvch *ZoneVideoChannelsHandler) ListVideoChannels() (videoChannels []*VideoChannel, err error) { videoChannels = make([]*VideoChannel, 0) _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { for _, videoChannel := range zvch.VideoChannels { videoChannels = append(videoChannels, videoChannel) } return }) return } func (zvch *ZoneVideoChannelsHandler) AddNewVideoChannel(channelName string, owner string, channelType string, members []interface{}) (err error) { if channelName == "" { err = fmt.Errorf("not a valid channel name provided") return } if _, ok := zvch.VideoChannels[channelName]; ok { err = fmt.Errorf("an video channel with this name already exist") return } mkdirErr := os.MkdirAll(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelName), 0700) if mkdirErr != nil { return mkdirErr } file, ferr := os.Create(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelName, "videoChannelConfig.json")) defer func() { _ = file.Close() }() if ferr != nil { return ferr } m := make([]string, 0, len(members)) for _, member := range members { if mbr, ok := member.(string); ok && mbr != owner { m = append(m, mbr) } } baseConfig := &VideoChannelConfig{ ID: channelName, Owner: owner, ChannelType: channelType, Members: append(m, owner), } bs, jsonErr := json.Marshal(baseConfig) if jsonErr != nil { return jsonErr } if _, writeErr := file.WriteString(string(bs)); writeErr != nil { return writeErr } var vcc VideoChannelConfig if err = json.Unmarshal(bs, &vcc); err != nil { return err } vc := NewVideoChannel(vcc.ID, vcc.Owner, vcc.ChannelType, vcc.Members, make([]string, 0), make(map[string]*VideoChannelMember)) _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { vc.CurrentMembers = make(map[string]*VideoChannelMember) zvch.VideoChannels[vc.ID] = vc return }) newVideoChannelForMembers := func(members []string) (err error) { for _, member := range members { zvch.sendZoneRequest(ADD_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ "userId": member, "channelId": channelName, }) done, e := zvch.sendDataChannelMessage("get_video_channels_response", "node", member, map[string]interface{}{ "videoChannels": []*VideoChannel{zvch.VideoChannels[channelName]}, }) select { case <-done: case err = <-e: return } } return } switch vc.ChannelType { case BROADCAST: fallthrough case PUBLIC: err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { err = newVideoChannelForMembers(zvch.ZoneMembersId) return }) case PRIVATE: err = newVideoChannelForMembers(vc.Members) } return } func (zvch *ZoneVideoChannelsHandler) DeleteVideoChannel(channelId string) (err error) { if err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { defer delete(zvch.VideoChannels, channelId) if _, ok := zvch.VideoChannels[channelId]; !ok { err = fmt.Errorf("no corresponding video channel") return } removeKnownVideoChannels := func(members []string) (err error) { for _, member := range members { zvch.sendZoneRequest(REMOVE_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ "userId": member, "channelId": channelId, }) done, e := zvch.sendDataChannelMessage(REMOVED_FROM_VIDEO_CHANNEL, "node", member, map[string]interface{}{ "channelId": channelId, "userId": member, }) select { case <-done: continue case err = <-e: return } } return } switch zvch.VideoChannels[channelId].ChannelType { case BROADCAST: fallthrough case PUBLIC: err = removeKnownVideoChannels(zvch.ZoneMembersId) case PRIVATE: err = removeKnownVideoChannels(zvch.VideoChannels[channelId].Members) } return }); err != nil { return } if err = os.RemoveAll(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId)); err != nil { return } return } func (zvch *ZoneVideoChannelsHandler) EditVideoChannelName(channelId string, newVideoChannelId string) (err error) { if _, ok := zvch.VideoChannels[channelId]; !ok { err = fmt.Errorf("no coresponding videoChannel") return } bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json")) if err != nil { return } var videoChannelConfig VideoChannelConfig if err = json.Unmarshal(bs, &videoChannelConfig); err != nil { return } videoChannelConfig.ID = newVideoChannelId bs, err = json.Marshal(&videoChannelConfig) if err = os.Rename(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId), filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", newVideoChannelId)); err != nil { return } f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", newVideoChannelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) defer func() { _ = f.Close() }() if err != nil { return err } if _, err = f.Write(bs); err != nil { return err } videoChannel := NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember)) _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { defer delete(zvch.VideoChannels, channelId) zvch.VideoChannels[newVideoChannelId] = videoChannel updateKnownVideoChannels := func(members []string) (err error) { for _, member := range members { zvch.sendZoneRequest(ADD_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ "userId": member, "channelId": newVideoChannelId, }) zvch.sendZoneRequest(REMOVE_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ "userId": member, "channelId": channelId, }) done, e := zvch.sendDataChannelMessage(VIDEO_CHANNNEL_NAME_EDITED, "node", member, map[string]interface{}{ "formerVideoChannelId": channelId, "newVideoChannelId": newVideoChannelId, }) select { case <-done: case channelErr := <-e: logger.Println(channelErr) } } return } switch videoChannel.ChannelType { case BROADCAST: fallthrough case PUBLIC: err = updateKnownVideoChannels(zvch.ZoneMembersId) case PRIVATE: err = updateKnownVideoChannels(videoChannel.Members) } return }) return } func (zvch *ZoneVideoChannelsHandler) EditVideoChannelType(channelId string, channelType string) (err error) { if _, ok := zvch.VideoChannels[channelId]; !ok { err = fmt.Errorf("no coresponding videoChannel") return } bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json")) if err != nil { return } var videoChannelConfig VideoChannelConfig if err = json.Unmarshal(bs, &videoChannelConfig); err != nil { return } videoChannelConfig.ChannelType = channelType bs, err = json.Marshal(&videoChannelConfig) f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) defer func() { _ = f.Close() }() if err != nil { return err } if _, err = f.Write(bs); err != nil { return err } videoChannel := NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember)) switch channelType { case BROADCAST: fallthrough case PUBLIC: var members = []string{} _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { members = append(members, zvch.ZoneMembersId...) return }) for _, member := range zvch.ZoneMembersId { if pubErr := zvch.AddVideoChannelsMembers(channelId, []any{member}); pubErr != nil { logger.Println(pubErr) } zvch.sendDataChannelMessage(VIDEO_CHANNNEL_TYPE_EDITED, "node", member, map[string]interface{}{ "channelId": channelId, "channelType": channelType, }) } case PRIVATE: for _, member := range zvch.ZoneMembersId { zvch.sendDataChannelMessage(VIDEO_CHANNNEL_TYPE_EDITED, "node", member, map[string]interface{}{ "channelId": channelId, "channelType": channelType, }) } } _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { zvch.VideoChannels[channelId] = videoChannel return }) return } func (zvch *ZoneVideoChannelsHandler) AddVideoChannelsMembers(channelId string, members []interface{}) (err error) { if _, ok := zvch.VideoChannels[channelId]; !ok { err = fmt.Errorf("no coresponding videoChannel") return } membersStr, err := ToStringSlice(members) if err != nil { return } bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json")) if err != nil { return } var videoChannelConfig VideoChannelConfig if err = json.Unmarshal(bs, &videoChannelConfig); err != nil { return } logger.Printf("%s - %s - %s -%v\n", videoChannelConfig.ID, videoChannelConfig.ChannelType, videoChannelConfig.Owner, members) addedMembers := make([]string, 0) memberLoop: for _, videoChannelMember := range membersStr { logger.Println("entering broadcast loop") for _, member := range videoChannelConfig.Members { if member == videoChannelMember { continue memberLoop } } videoChannelConfig.Members = append(videoChannelConfig.Members, videoChannelMember) addedMembers = append(addedMembers, videoChannelMember) logger.Println("sending zone request", ADD_KNOWN_VIDEO_CHANNEL) zvch.sendZoneRequest(ADD_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ "userId": videoChannelMember, "channelId": channelId, }) logger.Println("--------------done") } bs, err = json.Marshal(&videoChannelConfig) if err != nil { return } f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) defer func() { _ = f.Close() }() if err != nil { return err } if _, err = f.Write(bs); err != nil { return err } vc := NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember)) _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { zvch.VideoChannels[channelId] = vc return }) for _, member := range vc.Members { for _, m := range addedMembers { if member == m { if m != videoChannelConfig.Owner { go func() { bs, err = json.Marshal(map[string]any{ "zoneId": zvch.ZoneId, "channelId": channelId, "zoneHost": NodeID, }) if err != nil { return } zvch.sendZoneRequest(CREATE_NOTIFICATION, "node", map[string]interface{}{ "type": "added_in_video_channel", "title": "Added in video channel 🎥", "body": fmt.Sprintf("Added in channel %s in zone %s", channelId, zvch.ZoneName), "isPushed": true, "payload": string(bs), "recipients": []string{m}, }) }() } done, e := zvch.sendDataChannelMessage(ADDED_IN_VIDEO_CHANNEL, "node", member, map[string]interface{}{ "videoChannel": vc, }) select { case <-done: case err = <-e: logger.Println(err) } } else if _, ok := zvch.DataChannels[member]; ok { done, e := zvch.sendDataChannelMessage(VIDEO_CHANNEL_MEMBER_ADDED, "node", member, map[string]interface{}{ "userId": m, "channelId": vc.ID, }) select { case <-done: case err = <-e: logger.Println(err) } } } } return } func (zvch *ZoneVideoChannelsHandler) RemoveVideoChannelMember(channelId string, channelMember string) (err error) { if _, ok := zvch.VideoChannels[channelId]; !ok { err = fmt.Errorf("no coresponding videoChannel") return } bs, err := os.ReadFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json")) if err != nil { return } var videoChannelConfig VideoChannelConfig if err = json.Unmarshal(bs, &videoChannelConfig); err != nil { logger.Println(string(bs)) logger.Println("json error right here") return } if channelMember == videoChannelConfig.Owner { err = fmt.Errorf("you cannot remove the owner from the videoChannel") return } var index int var contain bool for i, member := range videoChannelConfig.Members { if member == channelMember { index = i contain = true break } } if !contain { err = fmt.Errorf("member %s not in the channel %s", channelMember, channelId) return } videoChannelConfig.Members = append(videoChannelConfig.Members[:index], videoChannelConfig.Members[index+1:]...) bs, err = json.Marshal(&videoChannelConfig) if err != nil { logger.Println("json error there") return } f, err := os.OpenFile(filepath.Join(dataPath, "data", "zones", zvch.ZoneId, "videoChannels", channelId, "videoChannelConfig.json"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) defer func() { _ = f.Close() }() if err != nil { return } logger.Println(string(bs)) if _, err = f.Write(bs); err != nil { return } var vc *VideoChannel _ = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { vc = NewVideoChannel(videoChannelConfig.ID, videoChannelConfig.Owner, videoChannelConfig.ChannelType, videoChannelConfig.Members, make([]string, 0), make(map[string]*VideoChannelMember)) zvch.VideoChannels[channelId] = vc return }) zvch.sendZoneRequest(REMOVE_KNOWN_VIDEO_CHANNEL, "node", map[string]interface{}{ "userId": channelMember, "channelId": channelId, }) if channelMember != videoChannelConfig.Owner { bs, err = json.Marshal(map[string]any{ "zoneId": zvch.ZoneId, "channelId": channelId, "zoneHost": NodeID, }) if err != nil { return err } zvch.sendZoneRequest(CREATE_NOTIFICATION, "node", map[string]interface{}{ "type": "removed_from_video_channel", "title": "Removed from video channel ⛔️", "body": fmt.Sprintf("You have no longer access to the channel %s in zone %s", channelId, zvch.ZoneName), "isPushed": true, "payload": string(bs), "recipients": []string{channelMember}, }) } done, e := zvch.sendDataChannelMessage(REMOVED_FROM_VIDEO_CHANNEL, "node", channelMember, map[string]interface{}{ "channelId": channelId, "userId": channelMember, }) select { case <-done: case err = <-e: logger.Println(err) } broadcastLoop: for _, member := range vc.Members { if member == channelMember { continue broadcastLoop } done, e := zvch.sendDataChannelMessage(VIDEO_CHANNEL_MEMBER_REMOVED, "node", member, map[string]interface{}{ "userId": channelMember, "channelId": vc.ID, }) select { case <-done: case err = <-e: logger.Println(err) } } return } func (zvch *ZoneVideoChannelsHandler) SetAllPublicVideoChannelForUser(userId string) (err error) { videoChannels, err := zvch.ListVideoChannels() if err != nil { return } for _, videoChannel := range videoChannels { if videoChannel.ChannelType == PUBLIC || videoChannel.ChannelType == BROADCAST { if err = zvch.AddVideoChannelsMembers(videoChannel.ID, []interface{}{userId}); err != nil { logger.Println(err) } continue } } return } func (zvch *ZoneVideoChannelsHandler) RemoveUserFromAllVideoChannels(userId string) (err error) { videoChannels, err := zvch.ListVideoChannels() if err != nil { return } for _, videoChannel := range videoChannels { if err = zvch.RemoveVideoChannelMember(videoChannel.ID, userId); err != nil { continue } } return } func (zvch *ZoneVideoChannelsHandler) RemoveAllUserVideoChannels(userId string) (err error) { videoChannels, err := zvch.ListVideoChannels() if err != nil { return } for _, videoChannel := range videoChannels { if videoChannel.Owner == userId { if derr := zvch.DeleteVideoChannel(videoChannel.ID); derr != nil { logger.Println("**************", derr) } } } return } func (zvch *ZoneVideoChannelsHandler) JoinVideoChannel(channelId string, userId string, sdp string) (err error) { err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { if _, ok := zvch.VideoChannels[channelId]; !ok { err = fmt.Errorf("no video channel with corresponding id") return } videoChannel := zvch.VideoChannels[channelId] 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 err := <-e: logger.Println(err) } } } if videoChannel.ChannelType == PRIVATE { signalMembers(videoChannel.Members) } else { signalMembers(zvch.ZoneMembersId) } 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 *ZoneVideoChannelsHandler) LeaveVideoChannel(channelId string, userId string) (err error) { err = atomicallyExecute(zvch.VideoChannelsFlag, func() (err error) { if _, ok := zvch.VideoChannels[channelId]; !ok { err = fmt.Errorf("no video channel with corresponding id") return } videoChannel := zvch.VideoChannels[channelId] 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 err := <-e: logger.Println(err) } } } if videoChannel.ChannelType == PRIVATE { signalMembers(videoChannel.Members) } else { signalMembers(zvch.ZoneMembersId) } return }) return } func (zvch *ZoneVideoChannelsHandler) 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.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 *ZoneVideoChannelsHandler) handleZoneRequest(ctx context.Context, req *ZoneRequest) (err error) { switch req.ReqType { case LEAVE_ZONE: logger.Println("*-----------------handling leaving zone---------------*") if err = verifyFieldsString(req.Payload, "userId"); err != nil { return } for _, vc := range zvch.VideoChannels { 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_ZONE_AUTHORIZED_MEMBER): if err = verifyFieldsString(req.Payload, "userId"); err != nil { return } var index int var found bool for i, m := range zvch.ZoneMembersId { if m == req.Payload["userId"].(string) { index = i found = true break } } if !found { err = fmt.Errorf("no such member in zone") return } zvch.ZoneMembersId = append(zvch.ZoneMembersId[:index], zvch.ZoneMembersId[index+1:]...) if err = zvch.RemoveAllUserVideoChannels(req.Payload["userId"].(string)); err != nil { logger.Println("****______________", err) } err = zvch.RemoveUserFromAllVideoChannels(req.Payload["userId"].(string)) case string(NEW_AUTHORIZED_ZONE_MEMBER): if err = verifyFieldsString(req.Payload, "userId"); err != nil { return } var contain bool for _, m := range zvch.ZoneMembersId { if m == req.Payload["userId"].(string) { contain = true break } } if !contain { zvch.ZoneMembersId = append(zvch.ZoneMembersId, req.Payload["userId"].(string)) } err = zvch.SetAllPublicVideoChannelForUser(req.Payload["userId"].(string)) case JOIN_VIDEO_CHANNEL: 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 ADD_VIDEO_CHANNEL_MEMBERS: if err = verifyFieldsString(req.Payload, "channelId"); err != nil { return } if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil { return } err = zvch.AddVideoChannelsMembers(req.Payload["channelId"].(string), req.Payload["members"].([]interface{})) case REMOVE_VIDEO_CHANNEL_MEMBER: if err = verifyFieldsString(req.Payload, "channelId", "userId"); err != nil { return } err = zvch.RemoveVideoChannelMember(req.Payload["channelId"].(string), req.Payload["userId"].(string)) if err != nil { logger.Println("an error occured in add known videoChannel", err) return } case EDIT_VIDEO_CHANNEL_NAME: if err = verifyFieldsString(req.Payload, "channelId", "newVideoChannelId"); err != nil { return } if err = zvch.EditVideoChannelName(req.Payload["channelId"].(string), req.Payload["newVideoChannelId"].(string)); err != nil { return } case EDIT_VIDEO_CHANNEL_TYPE: if err = verifyFieldsString(req.Payload, "channelId", "channelType"); err != nil { return } if err = zvch.EditVideoChannelType(req.Payload["channelId"].(string), req.Payload["channelType"].(string)); err != nil { return } case DELETE_VIDEO_CHANNEL: if err = verifyFieldsString(req.Payload, "channelId"); err != nil { return } err = zvch.DeleteVideoChannel(req.Payload["channelId"].(string)) case CREATE_VIDEO_CHANNEL: if err = verifyFieldsString(req.Payload, "channelId", "owner", "channelType"); err != nil { return } if err = verifyFieldsSliceInterface(req.Payload, "members"); err != nil { return } err = zvch.AddNewVideoChannel(req.Payload["channelId"].(string), req.Payload["owner"].(string), req.Payload["channelType"].(string), req.Payload["members"].([]interface{})) case GET_VIDEO_CHANNELS: if err = verifyFieldsSliceInterface(req.Payload, "channelsId"); err != nil { return } err = zvch.GetVideoChannels(req.From, req.Payload["channelsId"].([]interface{})...) 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) { if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok { err = fmt.Errorf("no channel corresponging the one requested") return } err = zvch.VideoChannels[req.Payload["channelId"].(string)].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) { if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok { err = fmt.Errorf("no channel corresponging the one requested") return } err = zvch.VideoChannels[req.Payload["channelId"].(string)].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) { if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok { err = fmt.Errorf("no channel corresponging the one requested") return } err = zvch.VideoChannels[req.Payload["channelId"].(string)].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) { if _, ok := zvch.VideoChannels[req.Payload["channelId"].(string)]; !ok { err = fmt.Errorf("no channel corresponging the one requested") return } err = zvch.VideoChannels[req.Payload["channelId"].(string)].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 }