mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-09-26 20:31:11 +08:00
146 lines
3.3 KiB
Go
146 lines
3.3 KiB
Go
package webrtc
|
|
|
|
import (
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
"github.com/pion/sdp/v3"
|
|
"github.com/pion/webrtc/v4"
|
|
)
|
|
|
|
func (c *Conn) CreateOffer(medias []*core.Media) (string, error) {
|
|
// 1. Create transeivers with proper kind and direction
|
|
for _, media := range medias {
|
|
var err error
|
|
switch media.Direction {
|
|
case core.DirectionRecvonly:
|
|
_, err = c.pc.AddTransceiverFromKind(
|
|
webrtc.NewRTPCodecType(media.Kind),
|
|
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionRecvonly},
|
|
)
|
|
case core.DirectionSendonly:
|
|
_, err = c.pc.AddTransceiverFromTrack(
|
|
NewTrack(media.Kind),
|
|
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly},
|
|
)
|
|
case core.DirectionSendRecv:
|
|
// default transceiver is sendrecv
|
|
_, err = c.pc.AddTransceiverFromTrack(NewTrack(media.Kind))
|
|
default:
|
|
// Nest cameras require data channel
|
|
_, err = c.pc.CreateDataChannel(media.Kind, nil)
|
|
}
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
// 2. Create local offer
|
|
desc, err := c.pc.CreateOffer(nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// 3. Start gathering phase
|
|
if err = c.pc.SetLocalDescription(desc); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return c.pc.LocalDescription().SDP, nil
|
|
}
|
|
|
|
func (c *Conn) CreateCompleteOffer(medias []*core.Media) (string, error) {
|
|
if _, err := c.CreateOffer(medias); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
<-webrtc.GatheringCompletePromise(c.pc)
|
|
return c.pc.LocalDescription().SDP, nil
|
|
}
|
|
|
|
func (c *Conn) SetAnswer(answer string) (err error) {
|
|
desc := webrtc.SessionDescription{
|
|
Type: webrtc.SDPTypeAnswer,
|
|
SDP: fakeFormatsInAnswer(c.pc.LocalDescription().SDP, answer),
|
|
}
|
|
if err = c.pc.SetRemoteDescription(desc); err != nil {
|
|
return
|
|
}
|
|
|
|
sd := &sdp.SessionDescription{}
|
|
if err = sd.Unmarshal([]byte(answer)); err != nil {
|
|
return
|
|
}
|
|
|
|
c.Medias = UnmarshalMedias(sd.MediaDescriptions)
|
|
|
|
return nil
|
|
}
|
|
|
|
// fakeFormatsInAnswer - fix pion bug with remote SDP parsing:
|
|
// pion will process formats only from first media of each kind
|
|
// so we add all formats from first offer media to the first answer media
|
|
func fakeFormatsInAnswer(offer, answer string) string {
|
|
sd2 := &sdp.SessionDescription{}
|
|
if err := sd2.Unmarshal([]byte(answer)); err != nil {
|
|
return answer
|
|
}
|
|
|
|
// check if answer has recvonly audio
|
|
var ok bool
|
|
for _, md2 := range sd2.MediaDescriptions {
|
|
if md2.MediaName.Media == "audio" {
|
|
if _, ok = md2.Attribute("recvonly"); ok {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if !ok {
|
|
return answer
|
|
}
|
|
|
|
sd1 := &sdp.SessionDescription{}
|
|
if err := sd1.Unmarshal([]byte(offer)); err != nil {
|
|
return answer
|
|
}
|
|
|
|
var formats []string
|
|
var attrs []sdp.Attribute
|
|
|
|
for _, md1 := range sd1.MediaDescriptions {
|
|
if md1.MediaName.Media == "audio" {
|
|
for _, attr := range md1.Attributes {
|
|
switch attr.Key {
|
|
case "rtpmap", "fmtp", "rtcp-fb", "extmap":
|
|
attrs = append(attrs, attr)
|
|
}
|
|
}
|
|
|
|
formats = md1.MediaName.Formats
|
|
break
|
|
}
|
|
}
|
|
|
|
for _, md2 := range sd2.MediaDescriptions {
|
|
if md2.MediaName.Media == "audio" {
|
|
for _, attr := range md2.Attributes {
|
|
switch attr.Key {
|
|
case "rtpmap", "fmtp", "rtcp-fb", "extmap":
|
|
default:
|
|
attrs = append(attrs, attr)
|
|
}
|
|
}
|
|
|
|
md2.MediaName.Formats = formats
|
|
md2.Attributes = attrs
|
|
break
|
|
}
|
|
}
|
|
|
|
b, err := sd2.Marshal()
|
|
if err != nil {
|
|
return answer
|
|
}
|
|
|
|
return string(b)
|
|
}
|