Zippytal-Node/webrtcFsManager.go
2021-12-08 15:58:40 +01:00

442 lines
13 KiB
Go

package localserver
import (
context "context"
"encoding/json"
"fmt"
"io"
"strconv"
sync "sync"
"github.com/pion/webrtc/v3"
)
type WebrtcFsManager struct {
stream GrpcManager_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) 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.stream.Send(&Request{
Type: string(WEBRTC_OFFER_FS),
From: "lolo_local_serv",
Token: "none",
Payload: map[string]string{
"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 _, candidate := 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 _, candidate := 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 id, 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
}