diff --git a/cmd/webrtc/candidates.go b/cmd/webrtc/candidates.go new file mode 100644 index 00000000..e33888b3 --- /dev/null +++ b/cmd/webrtc/candidates.go @@ -0,0 +1,75 @@ +package webrtc + +import ( + "github.com/AlexxIT/go2rtc/cmd/api" + "github.com/AlexxIT/go2rtc/pkg/streamer" + "github.com/AlexxIT/go2rtc/pkg/webrtc" + "github.com/pion/sdp/v3" + "strings" +) + +var candidates []string + +func AddCandidate(address string) { + candidates = append(candidates, address) +} + +func addCanditates(answer string) (string, error) { + if len(candidates) == 0 { + return answer, nil + } + + sd := &sdp.SessionDescription{} + if err := sd.Unmarshal([]byte(answer)); err != nil { + return "", err + } + + md := sd.MediaDescriptions[0] + + _, end := md.Attribute("end-of-candidates") + if end { + md.Attributes = md.Attributes[:len(md.Attributes)-1] + } + + for _, address := range candidates { + if strings.HasPrefix(address, "stun:") { + ip, err := webrtc.GetPublicIP() + if err != nil { + log.Warn().Err(err).Msg("[webrtc] public IP") + continue + } + address = ip.String() + address[4:] + + log.Debug().Str("addr", address).Msg("[webrtc] stun public address") + } + + cand, err := webrtc.NewCandidate(address) + if err != nil { + log.Warn().Err(err).Msg("[webrtc] candidate") + continue + } + + md.WithPropertyAttribute(cand) + } + + if end { + md.WithPropertyAttribute("end-of-candidates") + } + + data, err := sd.Marshal() + if err != nil { + return "", err + } + + return string(data), nil +} + +func candidateHandler(ctx *api.Context, msg *streamer.Message) { + if ctx.Consumer == nil { + return + } + if conn := ctx.Consumer.(*webrtc.Conn); conn != nil { + log.Trace().Str("candidate", msg.Value.(string)).Msg("[webrtc] remote") + conn.Push(msg) + } +} diff --git a/cmd/webrtc/webrtc.go b/cmd/webrtc/webrtc.go index a8780c7e..eccd1b13 100644 --- a/cmd/webrtc/webrtc.go +++ b/cmd/webrtc/webrtc.go @@ -8,10 +8,7 @@ import ( "github.com/AlexxIT/go2rtc/pkg/webrtc" pion "github.com/pion/webrtc/v3" "github.com/rs/zerolog" - "io/ioutil" "net" - "net/http" - "strings" ) func Init() { @@ -56,112 +53,15 @@ func Init() { candidates = cfg.Mod.Candidates - api.HandleFunc("/api/webrtc", apiHandler) - api.HandleFunc("/api/webrtc/camera", cameraHandler) api.HandleWS(webrtc.MsgTypeOffer, offerHandler) api.HandleWS(webrtc.MsgTypeCandidate, candidateHandler) } -func AddCandidate(address string) { - candidates = append(candidates, address) -} - var Port string var log zerolog.Logger -var candidates []string var NewPConn func() (*pion.PeerConnection, error) -func apiHandler(w http.ResponseWriter, r *http.Request) { - url := r.URL.Query().Get("url") - stream := streams.Get(url) - if stream == nil { - return - } - - // get offer - offer, err := ioutil.ReadAll(r.Body) - if err != nil { - log.Error().Err(err).Msg("[webrtc] read offer") - return - } - - // create new webrtc instance - cons := new(webrtc.Conn) - cons.Conn, err = NewPConn() - if err != nil { - log.Error().Err(err).Msg("[webrtc] new conn") - return - } - - cons.UserAgent = r.UserAgent() - cons.Listen(func(msg interface{}) { - if msg == streamer.StateNull { - stream.RemoveConsumer(cons) - } - }) - - if err = stream.AddConsumer(cons); err != nil { - log.Warn().Err(err).Msg("[api.webrtc] add consumer") - return - } - - cons.Init() - - // exchange sdp with waiting all candidates - answer, err := cons.ExchangeSDP(string(offer), true) - - // send SDP to client - if _, err = w.Write([]byte(answer)); err != nil { - log.Error().Err(err).Msg("[api.webrtc] send answer") - } -} - -func cameraHandler(w http.ResponseWriter, r *http.Request) { - url := r.URL.Query().Get("url") - stream := streams.Get(url) - if stream == nil { - return - } - - // get offer - offer, err := ioutil.ReadAll(r.Body) - if err != nil { - log.Error().Err(err).Msg("[webrtc] read offer") - return - } - - // create new webrtc instance - conn := new(webrtc.Conn) - conn.Conn, err = NewPConn() - if err != nil { - log.Error().Err(err).Msg("[webrtc] new conn") - return - } - - conn.UserAgent = r.UserAgent() - conn.Listen(func(msg interface{}) { - switch msg.(type) { - case pion.PeerConnectionState: - if msg == pion.PeerConnectionStateDisconnected { - stream.RemoveConsumer(conn) - } - case streamer.Track: - //stream.AddProducer(conn) - } - }) - - conn.Init() - - // exchange sdp with waiting all candidates - answer, err := conn.ExchangeSDP(string(offer), true) - - // send SDP to client - if _, err = w.Write([]byte(answer)); err != nil { - log.Error().Err(err).Msg("[api.webrtc] send answer") - } -} - func offerHandler(ctx *api.Context, msg *streamer.Message) { name := ctx.Request.URL.Query().Get("url") stream := streams.Get(name) @@ -216,7 +116,11 @@ func offerHandler(ctx *api.Context, msg *streamer.Message) { // exchange sdp without waiting all candidates //answer, err := conn.ExchangeSDP(offer, false) - answer, err := conn.GetAnswer() + //answer, err := conn.GetAnswer() + answer, err := conn.GetCompleteAnswer() + if err == nil { + answer, err = addCanditates(answer) + } log.Trace().Msgf("[webrtc] answer\n%s", answer) if err != nil { @@ -229,29 +133,6 @@ func offerHandler(ctx *api.Context, msg *streamer.Message) { Type: webrtc.MsgTypeAnswer, Value: answer, }) - for _, address := range candidates { - if strings.HasPrefix(address, "stun:") { - ip, err := webrtc.GetPublicIP() - if err != nil { - log.Warn().Err(err).Msg("[webrtc] public IP") - continue - } - address = ip.String() + address[4:] - - log.Debug().Str("addr", address).Msg("[webrtc] stun public address") - } - - cand, err := webrtc.NewCandidate(address) - if err != nil { - log.Warn().Err(err).Msg("[webrtc] candidate") - continue - } - - conn.Fire(&streamer.Message{ - Type: webrtc.MsgTypeCandidate, Value: cand, - }) - } - ctx.Consumer = conn } @@ -295,6 +176,9 @@ func ExchangeSDP( // exchange sdp without waiting all candidates //answer, err := conn.ExchangeSDP(offer, false) answer, err = conn.GetCompleteAnswer() + if err == nil { + answer, err = addCanditates(answer) + } log.Trace().Msgf("[webrtc] answer\n%s", answer) if err != nil { @@ -303,13 +187,3 @@ func ExchangeSDP( return } - -func candidateHandler(ctx *api.Context, msg *streamer.Message) { - if ctx.Consumer == nil { - return - } - if conn := ctx.Consumer.(*webrtc.Conn); conn != nil { - log.Trace().Str("candidate", msg.Value.(string)).Msg("[webrtc] remote") - conn.Push(msg) - } -} diff --git a/pkg/webrtc/conn.go b/pkg/webrtc/conn.go index f0168966..5446ec44 100644 --- a/pkg/webrtc/conn.go +++ b/pkg/webrtc/conn.go @@ -75,76 +75,6 @@ func (c *Conn) Init() { }) } -func (c *Conn) ExchangeSDP(offer string, complete bool) (answer string, err error) { - sdOffer := webrtc.SessionDescription{ - Type: webrtc.SDPTypeOffer, SDP: offer, - } - if err = c.Conn.SetRemoteDescription(sdOffer); err != nil { - return - } - - //for _, tr := range c.Conn.GetTransceivers() { - // switch tr.Direction() { - // case webrtc.RTPTransceiverDirectionSendonly: - // // disable transceivers if we don't have track - // // make direction=inactive - // // don't really necessary, but anyway - // if tr.Sender() == nil { - // if err = tr.Stop(); err != nil { - // return - // } - // } - // case webrtc.RTPTransceiverDirectionRecvonly: - // // TODO: change codecs list - // caps := webrtc.RTPCodecCapability{ - // MimeType: webrtc.MimeTypePCMU, - // ClockRate: 8000, - // } - // codecs := []webrtc.RTPCodecParameters{ - // {RTPCodecCapability: caps}, - // } - // if err = tr.SetCodecPreferences(codecs); err != nil { - // return - // } - // } - //} - - var sdAnswer webrtc.SessionDescription - sdAnswer, err = c.Conn.CreateAnswer(nil) - if err != nil { - return - } - - //var sd *sdp.SessionDescription - //sd, err = sdAnswer.Unmarshal() - //for _, media := range sd.MediaDescriptions { - // if media.MediaName.Media != "audio" { - // continue - // } - // for i, attr := range media.Attributes { - // if attr.Key == "sendonly" { - // attr.Key = "inactive" - // media.Attributes[i] = attr - // break - // } - // } - //} - //var b []byte - //b, err = sd.Marshal() - //sdAnswer.SDP = string(b) - - if err = c.Conn.SetLocalDescription(sdAnswer); err != nil { - return - } - - if complete { - <-webrtc.GatheringCompletePromise(c.Conn) - return c.Conn.LocalDescription().SDP, nil - } - - return sdAnswer.SDP, nil -} - func (c *Conn) SetOffer(offer string) (err error) { sdOffer := webrtc.SessionDescription{ Type: webrtc.SDPTypeOffer, SDP: offer,