Zippytal-Node/zoneAudioChannel.go

760 lines
25 KiB
Go

package localserver
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"strconv"
sync "sync"
"sync/atomic"
"time"
"github.com/google/uuid"
"github.com/pion/rtcp"
"github.com/pion/webrtc/v3"
)
const (
AUDIO_CHANNEL_ACCESS_DENIED ReqType = "audio_channel_access_denied"
AUDIO_CHANNEL_STOP_CALL ReqType = "audio_channel_stop_call"
AUDIO_CHANNEL_ACCESS_GRANTED ReqType = "audio_channel_access_granted"
AUDIO_CHANNEL_WEBRTC_OFFER ReqType = "audio_channel_offer"
AUDIO_CHANNEL_WEBRTC_ANSWER ReqType = "audio_channel_answer"
AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER ReqType = "audio_channel_rennegotiation_offer"
AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER ReqType = "audio_channel_rennegotiation_answer"
AUDIO_CHANNEL_WEBRTC_COUNTER_OFFER ReqType = "audio_channel_webrtc_counter_offer"
AUDIO_CHANNEL_WEBRTC_CANDIDATE ReqType = "audio_channel_webrtc_candidate"
AUDIO_CHANNEL_REMOVE_VIDEO ReqType = "audio_channel_remove_video"
GET_AUDIO_CHANNEL_TRACKS ReqType = "audio_channel_get_tracks"
)
const (
AUDIO_CHANNEL_USER_MUTE = "audio_channel_user_mute"
AUDIO_CHANNEL_USER_UNMUTE = "audio_channel_user_unmute"
AUDIO_CHANNEL_USER_SPEAKING = "audio_channel_user_speaking"
AUDIO_CHANNEL_USER_STOPPED_SPEAKING = "audio_channel_user_stopped_speaking"
)
type ZoneRTCPeerConnection struct {
*webrtc.PeerConnection
makingOffer bool
makingOfferLock *sync.Mutex
negotiate func(string, SendDCMessageFunc)
}
type AudioChannel struct {
ID string `json:"id"`
Owner string `json:"owner"`
ChannelType string `json:"channelType"`
Members []string `json:"members"`
CurrentMembersId []string `json:"currentMembersId"`
CurrentMembers map[string]*AudioChannelMember `json:"currentMembers"`
localSD map[string]*webrtc.SessionDescription `json:"-"`
rtcPeerConnections map[string]*ZoneRTCPeerConnection `json:"-"`
audioTransceiver map[string][]*PeerSender `json:"-"`
audiChannelsDataChannels map[string]*DataChannel `json:"-"`
pendingCandidates map[string][]*webrtc.ICECandidate `json:"-"`
remoteTracks map[string][]*RemoteTrack `json:"-"`
middlewares []interface{} `json:"-"`
candidateFlag *uint32 `json:"-"`
remoteTracksFlag *uint32 `json:"-"`
rtcPeerConnectionMapFlag *uint32 `json:"-"`
dataChannelMapFlag *uint32 `json:"-"`
localSDMapFlag *uint32 `json:"-"`
audioSenderFlag *uint32 `json:"-"`
}
type AudioChannelOnICECandidateFunc = func(string, string, *webrtc.ICECandidate) error
func NewAudioChannel(id string, owner string, channelType string, members []string, currentMembersId []string, currentMembers map[string]*AudioChannelMember) (audioChannel *AudioChannel) {
candidateFlag := uint32(0)
remoteTracksFlag := uint32(0)
rtcPeerConnectionMapFlag := uint32(0)
dataChannelMapFlag := uint32(0)
localSDMapFlag := uint32(0)
audioSenderFlag := uint32(0)
audioChannel = &AudioChannel{
ID: id,
Owner: owner,
ChannelType: channelType,
Members: members,
CurrentMembersId: currentMembersId,
CurrentMembers: currentMembers,
localSD: make(map[string]*webrtc.SessionDescription),
rtcPeerConnections: make(map[string]*ZoneRTCPeerConnection),
audioTransceiver: make(map[string][]*PeerSender),
audiChannelsDataChannels: make(map[string]*DataChannel),
pendingCandidates: make(map[string][]*webrtc.ICECandidate),
remoteTracks: make(map[string][]*RemoteTrack),
middlewares: make([]interface{}, 0),
candidateFlag: &candidateFlag,
remoteTracksFlag: &remoteTracksFlag,
rtcPeerConnectionMapFlag: &rtcPeerConnectionMapFlag,
dataChannelMapFlag: &dataChannelMapFlag,
localSDMapFlag: &localSDMapFlag,
audioSenderFlag: &audioSenderFlag,
}
return
}
func (ac *AudioChannel) HandleOffer(ctx context.Context, channelId string, userId string, sdp string, hostId string, sendDCMessage SendDCMessageFunc, cb AudioChannelOnICECandidateFunc) (done chan struct{}, errCh chan error) {
done, errCh = make(chan struct{}), make(chan error)
go func() {
peerConnection, err := ac.createPeerConnection(userId, ac.ID, webrtc.SDPTypeAnswer, cb, sendDCMessage)
if err != nil {
errCh <- err
return
}
_ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
ac.rtcPeerConnections[userId] = &ZoneRTCPeerConnection{
PeerConnection: peerConnection,
makingOffer: false,
makingOfferLock: &sync.Mutex{},
negotiate: ac.negotiate,
}
return
})
offer := webrtc.SessionDescription{
Type: webrtc.SDPTypeOffer,
SDP: sdp,
}
if err = peerConnection.SetRemoteDescription(offer); err != nil {
errCh <- err
return
}
rawAnswer, err := peerConnection.CreateAnswer(nil)
if err != nil {
errCh <- err
return
}
if err = peerConnection.SetLocalDescription(rawAnswer); err != nil {
errCh <- err
return
}
_, _ = sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_ANSWER), hostId, userId, map[string]interface{}{
"to": userId,
"from": ac.ID,
"channelId": channelId,
"sdp": rawAnswer.SDP,
})
done <- struct{}{}
logger.Println("handle offer done")
}()
return
}
func (ac *AudioChannel) HandleCounterOffer(ctx context.Context, userId string, sendDCMessage SendDCMessageFunc) (err error) {
// if err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
// if _, ok := ac.rtcPeerConnections[userId]; !ok {
// err = fmt.Errorf("no field corresponding peer connection for id %s", userId)
// return
// }
// logger.Println("handling counter offer")
// connection := ac.rtcPeerConnections[userId]
// err = atomicallyExecute(ac.localSDMapFlag, func() (err error) {
// err = connection.SetLocalDescription(*ac.localSD[userId])
// return
// })
// return
// }); err != nil {
// return
// }
// _ = atomicallyExecute(ac.localSDMapFlag, func() (err error) {
// delete(ac.localSD, userId)
// return
// })
if err = atomicallyExecute(ac.candidateFlag, func() (err error) {
for _, candidate := range ac.pendingCandidates[userId] {
logger.Println("sending candidate to", userId, candidate)
d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_CANDIDATE), "", userId, map[string]interface{}{
"from": ac.ID,
"to": userId,
"candidate": candidate.ToJSON().Candidate,
"sdpMid": *candidate.ToJSON().SDPMid,
"sdpMLineIndex": strconv.Itoa(int(*candidate.ToJSON().SDPMLineIndex)),
})
select {
case <-d:
case err = <-e:
return
}
}
delete(ac.pendingCandidates, userId)
return
}); err != nil {
return
}
return
}
func (ac *AudioChannel) HandleRennegotiationOffer(from string, sdp string, sendDCMessage SendDCMessageFunc) (err error) {
err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
if _, ok := ac.rtcPeerConnections[from]; !ok {
err = fmt.Errorf("no corresponding peer connection for id %s", from)
return
}
ac.rtcPeerConnections[from].makingOfferLock.Lock()
if ac.rtcPeerConnections[from].makingOffer {
ac.rtcPeerConnections[from].makingOfferLock.Unlock()
return fmt.Errorf("already making an offer or state is stable")
}
ac.rtcPeerConnections[from].makingOfferLock.Unlock()
if err = ac.rtcPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeOffer}); err != nil {
return
}
localSd, err := ac.rtcPeerConnections[from].CreateAnswer(nil)
if err != nil {
return
}
if err = ac.rtcPeerConnections[from].SetLocalDescription(localSd); err != nil {
return
}
d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_ANSWER), ac.ID, from, map[string]interface{}{
"from": ac.ID,
"to": from,
"sdp": localSd.SDP,
})
select {
case <-d:
case err = <-e:
}
return
})
return
}
func (ac *AudioChannel) HandleRennegotiationAnswer(from string, sdp string) (err error) {
logger.Println("---------------------handling rennego answer")
err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
ac.rtcPeerConnections[from].makingOfferLock.Lock()
if ac.rtcPeerConnections[from].makingOffer {
ac.rtcPeerConnections[from].makingOfferLock.Unlock()
return fmt.Errorf("already making an offer or state is stable")
}
ac.rtcPeerConnections[from].makingOfferLock.Unlock()
if _, ok := ac.rtcPeerConnections[from]; !ok {
err = fmt.Errorf("no corresponding peer connection for id %s", from)
return
}
err = ac.rtcPeerConnections[from].SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer})
return
})
return
}
func (ac *AudioChannel) AddCandidate(candidate *webrtc.ICECandidateInit, from string) (err error) {
logger.Println("adding ice candidate", candidate)
err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
if _, ok := ac.rtcPeerConnections[from]; ok && candidate != nil {
err = ac.rtcPeerConnections[from].AddICECandidate(*candidate)
}
return
})
return
}
func (ac *AudioChannel) createPeerConnection(target string, from string, peerType webrtc.SDPType, cb AudioChannelOnICECandidateFunc, sendDCMessage SendDCMessageFunc) (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
}
if e := ac.HandleDataChannelEvents(event.From, event.EventId, event.Payload); e != nil {
logger.Println("*-------------- datachannel error: ", e)
}
})
logger.Println("new channel for target : ", target)
channel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold)
channel.OnBufferedAmountLow(func() {
})
_ = atomicallyExecute(ac.dataChannelMapFlag, func() (err error) {
logger.Println(target)
l := uint32(0)
ac.audiChannelsDataChannels[target] = &DataChannel{
DataChannel: channel,
l: &l,
}
return
})
} else {
peerConnection.OnDataChannel(func(dc *webrtc.DataChannel) {
_ = atomicallyExecute(ac.dataChannelMapFlag, func() (err error) {
l := uint32(0)
ac.audiChannelsDataChannels[target] = &DataChannel{
DataChannel: dc,
l: &l,
}
return
})
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
}
if e := ac.HandleDataChannelEvents(event.From, event.EventId, event.Payload); e != nil {
logger.Println("*-------------- datachannel error: ", e)
}
})
})
}
err = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
err = atomicallyExecute(ac.remoteTracksFlag, func() (err error) {
logger.Println("------------------", ac.CurrentMembersId)
for _, id := range ac.CurrentMembersId {
logger.Println(id)
if id != target {
if _, ok := ac.remoteTracks[id]; !ok {
continue
}
for _, track := range ac.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
}
_ = atomicallyExecute(ac.audioSenderFlag, func() (err error) {
if len(ac.audioTransceiver) == 0 {
ac.audioTransceiver[id] = []*PeerSender{{ID: target, Transceiver: transceiver}}
} else {
ac.audioTransceiver[id] = append(ac.audioTransceiver[id], &PeerSender{ID: target, Transceiver: transceiver})
}
return
})
logger.Println("track added", track)
}
}
}
return
})
return
})
peerConnection.OnConnectionStateChange(func(pcs webrtc.PeerConnectionState) {
if pcs == webrtc.PeerConnectionStateClosed || pcs == webrtc.PeerConnectionStateDisconnected || pcs == webrtc.PeerConnectionStateFailed {
logger.Println(pcs)
//ac.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())
logger.Println("*************************----------------", i, "-----------------------***************")
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}
_ = atomicallyExecute(ac.remoteTracksFlag, func() (err error) {
if len(ac.remoteTracks[target]) == 0 {
ac.remoteTracks[target] = []*RemoteTrack{remote}
} else {
ac.remoteTracks[target] = append(ac.remoteTracks[target], remote)
}
index := len(ac.remoteTracks[target])
logger.Println(index, ac.remoteTracks)
return
})
_ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
for _, id := range ac.CurrentMembersId {
if id != target {
if _, ok := ac.rtcPeerConnections[id]; !ok {
continue
}
connection := ac.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 {
_ = atomicallyExecute(ac.audioSenderFlag, func() (err error) {
if len(ac.audioTransceiver) == 0 {
ac.audioTransceiver[target] = []*PeerSender{{ID: id, Transceiver: transceiver}}
} else {
ac.audioTransceiver[target] = append(ac.audioTransceiver[target], &PeerSender{ID: id, Transceiver: transceiver})
}
return
})
} else if localTrack.Kind() == webrtc.RTPCodecTypeVideo {
logger.Println("track of wrong type")
}
go func() {
<-time.After(time.Millisecond * 500)
connection.negotiate(id, sendDCMessage)
}()
}
}
return
})
d := make(chan struct{})
go func() {
for {
i, _, readErr := tr.Read(rtpbuf)
if readErr != nil {
logger.Println(readErr)
break
}
//logger.Println(rtpbuf[:i])
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]
}
}
}
d <- struct{}{}
}()
<-d
})
peerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
if i == nil {
return
}
_ = atomicallyExecute(ac.candidateFlag, func() (err error) {
desc := peerConnection.RemoteDescription()
if desc == nil {
logger.Println("generated candidate appended to list : ", i)
ac.pendingCandidates[target] = append(ac.pendingCandidates[target], i)
} else {
logger.Println("generated candidate : ", i)
if iceCandidateErr := cb(from, target, i); iceCandidateErr != nil {
logger.Println(iceCandidateErr)
}
}
return
})
})
//peerConnection.OnNegotiationNeeded(func() {
//logger.Println("---------------- rennego is needed -----------")
// _ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
// for _, id := range ac.CurrentMembersId {
// logger.Println("----------------- sending renego to peer with id", id)
// if _, ok := ac.rtcPeerConnections[id]; !ok {
// continue
// }
// if peerConnection.SignalingState() == webrtc.SignalingStateStable {
// localSd, localSdErr := peerConnection.CreateOffer(nil)
// if localSdErr != nil {
// logger.Println(localSdErr)
// return localSdErr
// }
// if err = peerConnection.SetLocalDescription(localSd); err != nil {
// logger.Println(err)
// return
// }
// d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER), ac.ID, id, map[string]interface{}{
// "from": ac.ID,
// "to": id,
// "sdp": localSd.SDP,
// })
// select {
// case <-d:
// case err = <-e:
// logger.Println(err)
// }
// }
// }
// return
// })
//})
return
}
func (ac *AudioChannel) HandleLeavingMember(id string) {
if err := atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
if _, ok := ac.rtcPeerConnections[id]; !ok {
err = fmt.Errorf("no corresponding peerconnection for audio channel leaving member")
}
return
}); err != nil {
logger.Println(err)
} else {
defer func() {
_ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
if _, ok := ac.rtcPeerConnections[id]; ok {
if closeErr := ac.rtcPeerConnections[id].Close(); closeErr != nil {
err = closeErr
logger.Println("peer connection close error", closeErr)
}
}
delete(ac.rtcPeerConnections, id)
return
})
}()
}
logger.Printf("peer %s is leaving the squad\n", id)
_ = atomicallyExecute(ac.dataChannelMapFlag, func() (err error) {
if _, ok := ac.audiChannelsDataChannels[id]; ok {
ac.audiChannelsDataChannels[id].DataChannel.Close()
}
delete(ac.audiChannelsDataChannels, id)
return
})
_ = atomicallyExecute(ac.localSDMapFlag, func() (err error) {
delete(ac.localSD, id)
return
})
_ = atomicallyExecute(ac.candidateFlag, func() (err error) {
delete(ac.pendingCandidates, id)
return
})
_ = atomicallyExecute(ac.audioSenderFlag, func() (err error) {
for peerId, peerSender := range ac.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++
}
}
ac.audioTransceiver[peerId] = ac.audioTransceiver[peerId][:len(peerSender)-(c)]
logger.Println(ac.audioTransceiver[peerId])
}
}
for _, transceiver := range ac.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(ac.audioTransceiver, id)
return
})
_ = atomicallyExecute(ac.remoteTracksFlag, func() (err error) {
delete(ac.remoteTracks, id)
return
})
}
func (ac *AudioChannel) negotiate(target string, sendDCMessage SendDCMessageFunc) {
logger.Println("------------------negotiate is called")
_ = atomicallyExecute(ac.rtcPeerConnectionMapFlag, func() (err error) {
if _, ok := ac.rtcPeerConnections[target]; !ok {
return
}
ac.rtcPeerConnections[target].makingOfferLock.Lock()
ac.rtcPeerConnections[target].makingOffer = true
ac.rtcPeerConnections[target].makingOfferLock.Unlock()
defer func() {
ac.rtcPeerConnections[target].makingOfferLock.Lock()
ac.rtcPeerConnections[target].makingOffer = false
ac.rtcPeerConnections[target].makingOfferLock.Unlock()
}()
for _, id := range ac.CurrentMembersId {
logger.Println("----------------- sending renego to peer with id", id)
if _, ok := ac.rtcPeerConnections[id]; !ok {
continue
}
connection := ac.rtcPeerConnections[id]
if connection.SignalingState() == webrtc.SignalingStateStable {
localSd, err := connection.CreateOffer(nil)
if err != nil {
logger.Println(err)
return err
}
if err = connection.SetLocalDescription(localSd); err != nil {
logger.Println(err)
return err
}
d, e := sendDCMessage(string(AUDIO_CHANNEL_WEBRTC_RENNEGOTIATION_OFFER), ac.ID, id, map[string]interface{}{
"from": ac.ID,
"to": id,
"sdp": localSd.SDP,
})
select {
case <-d:
case err = <-e:
logger.Println(err)
}
}
}
return
})
}
func (ac *AudioChannel) broadcastDatachannelMessage(from string, eventId string, payload map[string]interface{}) (done chan struct{}, errCh chan error) {
done, errCh = make(chan struct{}), make(chan error)
go func() {
bs, jsonErr := json.Marshal(&ZoneResponse{
Type: eventId,
From: ac.ID,
Payload: payload,
})
if jsonErr != nil {
errCh <- jsonErr
return
}
if err := atomicallyExecute(ac.dataChannelMapFlag, func() (err error) {
for id, dc := range ac.audiChannelsDataChannels {
if from != id {
if err = dc.DataChannel.SendText(string(bs)); err != nil {
return
}
}
}
return
}); err != nil {
errCh <- err
}
done <- struct{}{}
}()
return
}
func (ac *AudioChannel) HandleDataChannelEvents(from string, eventId string, payload map[string]interface{}) (err error) {
switch eventId {
case AUDIO_CHANNEL_USER_MUTE:
if err = atomicallyExecute(ac.remoteTracksFlag, func() (err error) {
if _, ok := ac.remoteTracks[from]; !ok {
err = fmt.Errorf("no corresponding remote tracks entry for id %s", from)
return
}
for _, track := range ac.remoteTracks[from] {
atomic.SwapInt32(track.rdv, 1)
}
return
}); err != nil {
return
}
done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_MUTE, map[string]interface{}{
"userId": from,
})
select {
case <-done:
case err = <-errCh:
}
case AUDIO_CHANNEL_USER_UNMUTE:
if err = atomicallyExecute(ac.remoteTracksFlag, func() (err error) {
if _, ok := ac.remoteTracks[from]; !ok {
err = fmt.Errorf("no corresponding remote tracks entry for id %s", from)
return
}
for _, track := range ac.remoteTracks[from] {
atomic.SwapInt32(track.rdv, 0)
}
return
}); err != nil {
return
}
done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_UNMUTE, map[string]interface{}{
"userId": from,
})
select {
case <-done:
case err = <-errCh:
}
case AUDIO_CHANNEL_USER_SPEAKING:
done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_SPEAKING, map[string]interface{}{
"userId": from,
})
select {
case <-done:
case err = <-errCh:
}
case AUDIO_CHANNEL_USER_STOPPED_SPEAKING:
done, errCh := ac.broadcastDatachannelMessage(from, AUDIO_CHANNEL_USER_STOPPED_SPEAKING, map[string]interface{}{
"userId": from,
})
select {
case <-done:
case err = <-errCh:
}
}
return
}