mirror of
https://github.com/bluenviron/mediamtx
synced 2024-12-14 02:34:52 +00:00
1989997fe1
* fix: whep gathering failure leaks peer connections * fix: failure to create whep client leaks when read fails, client is not closed * fix: close idle connection with whip client * fix: no link check on early whip client close * move http.Client.CloseIdleConnections() outside WHIPClient * automatically call WHEPClient.Close() in case of errors during WHEPClient.Read() or WHEPClient.Publish() --------- Co-authored-by: Jonathan Martin <jonathan.martin@marss.com> Co-authored-by: aler9 <46489434+aler9@users.noreply.github.com>
235 lines
4.8 KiB
Go
235 lines
4.8 KiB
Go
package webrtc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/bluenviron/gortsplib/v4/pkg/format"
|
|
"github.com/pion/sdp/v3"
|
|
|
|
"github.com/bluenviron/mediamtx/internal/logger"
|
|
)
|
|
|
|
// WHIPClient is a WHIP client.
|
|
type WHIPClient struct {
|
|
HTTPClient *http.Client
|
|
URL *url.URL
|
|
Log logger.Writer
|
|
|
|
pc *PeerConnection
|
|
}
|
|
|
|
// Publish publishes tracks.
|
|
func (c *WHIPClient) Publish(
|
|
ctx context.Context,
|
|
videoTrack format.Format,
|
|
audioTrack format.Format,
|
|
) ([]*OutgoingTrack, error) {
|
|
iceServers, err := WHIPOptionsICEServers(ctx, c.HTTPClient, c.URL.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
api, err := NewAPI(APIConf{
|
|
LocalRandomUDP: true,
|
|
IPsFromInterfaces: true,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.pc = &PeerConnection{
|
|
ICEServers: iceServers,
|
|
API: api,
|
|
Publish: true,
|
|
Log: c.Log,
|
|
}
|
|
err = c.pc.Start()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tracks, err := c.pc.SetupOutgoingTracks(videoTrack, audioTrack)
|
|
if err != nil {
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
offer, err := c.pc.CreatePartialOffer()
|
|
if err != nil {
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
res, err := PostOffer(ctx, c.HTTPClient, c.URL.String(), offer)
|
|
if err != nil {
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
c.URL, err = c.URL.Parse(res.Location)
|
|
if err != nil {
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
err = c.pc.SetAnswer(res.Answer)
|
|
if err != nil {
|
|
WHIPDeleteSession(context.Background(), c.HTTPClient, c.URL.String()) //nolint:errcheck
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
t := time.NewTimer(webrtcHandshakeTimeout)
|
|
defer t.Stop()
|
|
|
|
outer:
|
|
for {
|
|
select {
|
|
case ca := <-c.pc.NewLocalCandidate():
|
|
err := WHIPPatchCandidate(ctx, c.HTTPClient, c.URL.String(), offer, res.ETag, ca)
|
|
if err != nil {
|
|
WHIPDeleteSession(context.Background(), c.HTTPClient, c.URL.String()) //nolint:errcheck
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
case <-c.pc.GatheringDone():
|
|
|
|
case <-c.pc.Connected():
|
|
break outer
|
|
|
|
case <-t.C:
|
|
WHIPDeleteSession(context.Background(), c.HTTPClient, c.URL.String()) //nolint:errcheck
|
|
c.pc.Close()
|
|
return nil, fmt.Errorf("deadline exceeded while waiting connection")
|
|
}
|
|
}
|
|
|
|
return tracks, nil
|
|
}
|
|
|
|
// Read reads tracks.
|
|
func (c *WHIPClient) Read(ctx context.Context) ([]*IncomingTrack, error) {
|
|
iceServers, err := WHIPOptionsICEServers(ctx, c.HTTPClient, c.URL.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
api, err := NewAPI(APIConf{
|
|
LocalRandomUDP: true,
|
|
IPsFromInterfaces: true,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
c.pc = &PeerConnection{
|
|
ICEServers: iceServers,
|
|
API: api,
|
|
Publish: false,
|
|
Log: c.Log,
|
|
}
|
|
err = c.pc.Start()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
offer, err := c.pc.CreatePartialOffer()
|
|
if err != nil {
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
res, err := PostOffer(ctx, c.HTTPClient, c.URL.String(), offer)
|
|
if err != nil {
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
c.URL, err = c.URL.Parse(res.Location)
|
|
if err != nil {
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
var sdp sdp.SessionDescription
|
|
err = sdp.Unmarshal([]byte(res.Answer.SDP))
|
|
if err != nil {
|
|
WHIPDeleteSession(context.Background(), c.HTTPClient, c.URL.String()) //nolint:errcheck
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
// check that there are at most two tracks
|
|
_, err = TrackCount(sdp.MediaDescriptions)
|
|
if err != nil {
|
|
WHIPDeleteSession(context.Background(), c.HTTPClient, c.URL.String()) //nolint:errcheck
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
err = c.pc.SetAnswer(res.Answer)
|
|
if err != nil {
|
|
WHIPDeleteSession(context.Background(), c.HTTPClient, c.URL.String()) //nolint:errcheck
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
t := time.NewTimer(webrtcHandshakeTimeout)
|
|
defer t.Stop()
|
|
|
|
outer:
|
|
for {
|
|
select {
|
|
case ca := <-c.pc.NewLocalCandidate():
|
|
err := WHIPPatchCandidate(ctx, c.HTTPClient, c.URL.String(), offer, res.ETag, ca)
|
|
if err != nil {
|
|
WHIPDeleteSession(context.Background(), c.HTTPClient, c.URL.String()) //nolint:errcheck
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
case <-c.pc.GatheringDone():
|
|
|
|
case <-c.pc.Connected():
|
|
break outer
|
|
|
|
case <-t.C:
|
|
WHIPDeleteSession(context.Background(), c.HTTPClient, c.URL.String()) //nolint:errcheck
|
|
c.pc.Close()
|
|
return nil, fmt.Errorf("deadline exceeded while waiting connection")
|
|
}
|
|
}
|
|
|
|
tracks, err := c.pc.GatherIncomingTracks(ctx, 0)
|
|
if err != nil {
|
|
WHIPDeleteSession(context.Background(), c.HTTPClient, c.URL.String()) //nolint:errcheck
|
|
c.pc.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return tracks, nil
|
|
}
|
|
|
|
// Close closes the client.
|
|
func (c *WHIPClient) Close() error {
|
|
err := WHIPDeleteSession(context.Background(), c.HTTPClient, c.URL.String())
|
|
c.pc.Close()
|
|
return err
|
|
}
|
|
|
|
// Wait waits for client errors.
|
|
func (c *WHIPClient) Wait(ctx context.Context) error {
|
|
select {
|
|
case <-c.pc.Disconnected():
|
|
return fmt.Errorf("peer connection closed")
|
|
|
|
case <-ctx.Done():
|
|
return fmt.Errorf("terminated")
|
|
}
|
|
}
|