Implement ICE restarts for WHIP.
This commit is contained in:
@@ -8,6 +8,9 @@ import (
|
|||||||
|
|
||||||
"github.com/jech/galene/conn"
|
"github.com/jech/galene/conn"
|
||||||
"github.com/jech/galene/group"
|
"github.com/jech/galene/group"
|
||||||
|
"github.com/jech/galene/sdpfrag"
|
||||||
|
|
||||||
|
"github.com/pion/sdp/v3"
|
||||||
"github.com/pion/webrtc/v4"
|
"github.com/pion/webrtc/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -242,3 +245,57 @@ func (c *WhipClient) GotICECandidate(init webrtc.ICECandidateInit) error {
|
|||||||
}
|
}
|
||||||
return c.connection.addICECandidate(&init)
|
return c.connection.addICECandidate(&init)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *WhipClient) Restart(ctx context.Context, frag sdpfrag.SDPFrag) (sdpfrag.SDPFrag, error) {
|
||||||
|
c.mu.Lock()
|
||||||
|
conn := c.connection
|
||||||
|
c.mu.Unlock()
|
||||||
|
if conn == nil {
|
||||||
|
return sdpfrag.SDPFrag{}, errors.New("no connection")
|
||||||
|
}
|
||||||
|
|
||||||
|
offer := conn.pc.RemoteDescription()
|
||||||
|
var sdpOffer sdp.SessionDescription
|
||||||
|
err := sdpOffer.Unmarshal([]byte(offer.SDP))
|
||||||
|
if err != nil {
|
||||||
|
return sdpfrag.SDPFrag{}, nil
|
||||||
|
}
|
||||||
|
sdpOffer2, _ := sdpfrag.PatchSDP(sdpOffer, frag)
|
||||||
|
offer2, err := sdpOffer2.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return sdpfrag.SDPFrag{}, nil
|
||||||
|
}
|
||||||
|
err = conn.pc.SetRemoteDescription(webrtc.SessionDescription{
|
||||||
|
Type: webrtc.SDPTypeOffer,
|
||||||
|
SDP: string(offer2),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return sdpfrag.SDPFrag{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
answer, err := conn.pc.CreateAnswer(nil)
|
||||||
|
if err != nil {
|
||||||
|
return sdpfrag.SDPFrag{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gatherComplete := webrtc.GatheringCompletePromise(conn.pc)
|
||||||
|
|
||||||
|
err = conn.pc.SetLocalDescription(answer)
|
||||||
|
if err != nil {
|
||||||
|
return sdpfrag.SDPFrag{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return sdpfrag.SDPFrag{}, ctx.Err()
|
||||||
|
case <-gatherComplete:
|
||||||
|
}
|
||||||
|
|
||||||
|
sdpAnswer2 := conn.pc.LocalDescription()
|
||||||
|
var answer2 sdp.SessionDescription
|
||||||
|
err = answer2.Unmarshal([]byte(sdpAnswer2.SDP))
|
||||||
|
if err != nil {
|
||||||
|
return sdpfrag.SDPFrag{}, err
|
||||||
|
}
|
||||||
|
return sdpfrag.FromSDP(answer2), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -226,3 +226,50 @@ func PatchSDP(s sdp.SessionDescription, f SDPFrag) (sdp.SessionDescription, bool
|
|||||||
}
|
}
|
||||||
return s, override
|
return s, override
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCandidates(as []sdp.Attribute, mid *string, index *uint16, ufrag string) []webrtc.ICECandidateInit {
|
||||||
|
var cs []webrtc.ICECandidateInit
|
||||||
|
for _, a := range as {
|
||||||
|
if a.Key != "candidate" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c := webrtc.ICECandidateInit{
|
||||||
|
Candidate: a.Value,
|
||||||
|
SDPMid: mid,
|
||||||
|
SDPMLineIndex: index,
|
||||||
|
}
|
||||||
|
if ufrag != "" {
|
||||||
|
c.UsernameFragment = &ufrag
|
||||||
|
}
|
||||||
|
cs = append(cs, c)
|
||||||
|
}
|
||||||
|
return cs
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromSDP extracts the ICE information from an [sdp.SessionDescription]
|
||||||
|
func FromSDP(s sdp.SessionDescription) SDPFrag {
|
||||||
|
var f SDPFrag
|
||||||
|
ufrag, _ := s.Attribute("ice-ufrag")
|
||||||
|
f.UsernameFragment = ufrag
|
||||||
|
pwd, _ := s.Attribute("ice-pwd")
|
||||||
|
f.Password = pwd
|
||||||
|
f.Candidates = getCandidates(s.Attributes, nil, nil, ufrag)
|
||||||
|
|
||||||
|
for index, m := range s.MediaDescriptions {
|
||||||
|
var mm MediaDescription
|
||||||
|
mm.MLine = m.MediaName.String()
|
||||||
|
mid, _ := m.Attribute("mid")
|
||||||
|
mm.Mid = mid
|
||||||
|
ufrag, _ := m.Attribute("ice-ufrag")
|
||||||
|
mm.UsernameFragment = ufrag
|
||||||
|
pwd, _ := m.Attribute("ice-pwd")
|
||||||
|
mm.Password = pwd
|
||||||
|
i := uint16(index)
|
||||||
|
mm.Candidates = getCandidates(
|
||||||
|
m.Attributes, &mid, &i, ufrag,
|
||||||
|
)
|
||||||
|
f.MediaDescriptions = append(f.MediaDescriptions, mm)
|
||||||
|
}
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|||||||
@@ -300,3 +300,29 @@ func TestPatchSDP(t *testing.T) {
|
|||||||
testPatchSDP(t, sdpOffer, sdpfragRestart, "HWmk", 0)
|
testPatchSDP(t, sdpOffer, sdpfragRestart, "HWmk", 0)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFromSDP(t *testing.T) {
|
||||||
|
frag := `m=audio 9 UDP/TLS/RTP/SAVPF 111 63 9 0 8 13 110 126
|
||||||
|
a=ice-ufrag:qiKa
|
||||||
|
a=ice-pwd:bcfs93hb/+ZLuUE2K50HVbkr
|
||||||
|
a=mid:0
|
||||||
|
m=video 9 UDP/TLS/RTP/SAVPF 96 97 103 104 107 108 109 114 115 116 117 118 39 40 45 46 98 99 100 101 119 120 121
|
||||||
|
a=ice-ufrag:qiKa
|
||||||
|
a=ice-pwd:bcfs93hb/+ZLuUE2K50HVbkr
|
||||||
|
a=mid:1
|
||||||
|
`
|
||||||
|
var f SDPFrag
|
||||||
|
err := f.Unmarshal([]byte(frag))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
var s sdp.SessionDescription
|
||||||
|
err = s.Unmarshal([]byte(sdpOffer))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unmarshal: %v", err)
|
||||||
|
}
|
||||||
|
ff := FromSDP(s)
|
||||||
|
if !reflect.DeepEqual(ff, f) {
|
||||||
|
t.Errorf("Not equal: %v %v", ff, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+21
-2
@@ -381,11 +381,30 @@ func whipResourceHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
uu, pp := frag.UFragPwd()
|
uu, pp := frag.UFragPwd()
|
||||||
if uu != u || pp != p {
|
if uu != u || pp != p {
|
||||||
http.Error(w, "ICE restarts are not supported yet",
|
frag2, err := c.Restart(r.Context(), frag)
|
||||||
http.StatusUnprocessableEntity,
|
if err != nil {
|
||||||
|
log.Printf("WHIP restart: %v", err)
|
||||||
|
http.Error(w, "internal server error",
|
||||||
|
http.StatusInternalServerError,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
c.SetETag("\"" + newId() + "\"")
|
||||||
|
f2, err := frag2.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("WHIP marshal frag: %v", err)
|
||||||
|
http.Error(w, "internal server error",
|
||||||
|
http.StatusInternalServerError,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set(
|
||||||
|
"Content-Type", "application/trickle-ice-sdpfrag",
|
||||||
|
)
|
||||||
|
w.Header().Set("ETag", c.ETag())
|
||||||
|
w.Write(f2)
|
||||||
|
return
|
||||||
|
}
|
||||||
for _, init := range frag.AllCandidates() {
|
for _, init := range frag.AllCandidates() {
|
||||||
err := c.GotICECandidate(init)
|
err := c.GotICECandidate(init)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user