-
Notifications
You must be signed in to change notification settings - Fork 295
/
client.go
145 lines (123 loc) · 3.27 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package webrtc
import (
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/pion/sdp/v3"
"github.com/pion/webrtc/v3"
)
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)
}