provides cancelation when a component stop responding it closes all resources

This commit is contained in:
Leandro Moreira
2024-01-31 00:21:15 -03:00
parent 1e29f3c4ab
commit a903aa17d7
6 changed files with 97 additions and 43 deletions

13
FAQ.md
View File

@@ -1,5 +1,18 @@
# FAQ # FAQ
## I can't connect two tabs or browser at the same for the SRT
It doesn't work When I try to connect in another browser or tab, or even when I try to refresh the current page. It raises an seemingly timeout error.
```
astisrt: connecting failed: astisrt: connecting failed: astisrt: Connection setup failure: connection timed out
```
Apparently both `ffmpeg` and `srt-live-transmit` won't allow multiple persistent connections.
ref1 https://github.com/Haivision/srt/blob/master/docs/apps/srt-live-transmit.md#medium-srt
ref2 https://github.com/asticode/go-astisrt/issues/6#issuecomment-1917076767
## It's not working on Firefox/Chrome/Edge. ## It's not working on Firefox/Chrome/Edge.
[WebRTC establishes a baseline set of codecs which all compliant browsers are required to support. Some browsers may choose to allow other codecs as well.](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#supported_video_codecs) [WebRTC establishes a baseline set of codecs which all compliant browsers are required to support. Some browsers may choose to allow other codecs as well.](https://developer.mozilla.org/en-US/docs/Web/Media/Formats/WebRTC_codecs#supported_video_codecs)

View File

@@ -51,18 +51,15 @@ func NewSRTController(c *entities.Config, l *zap.Logger, lc fx.Lifecycle) (*SRTC
}, nil }, nil
} }
func (c *SRTController) Connect(params *entities.RequestParams) (*astisrt.Connection, error) { func (c *SRTController) Connect(cancel context.CancelFunc, params entities.RequestParams) (*astisrt.Connection, error) {
c.l.Sugar().Infow("trying to connect srt") c.l.Sugar().Infow("trying to connect srt")
if params == nil {
return nil, entities.ErrMissingRemoteOffer
}
if err := params.Valid(); err != nil { if err := params.Valid(); err != nil {
return nil, err return nil, err
} }
c.l.Sugar().Infow("Connecting to SRT ", c.l.Sugar().Infow("Connecting to SRT ",
"offer", params, "offer", params.String(),
) )
conn, err := astisrt.Dial(astisrt.DialOptions{ conn, err := astisrt.Dial(astisrt.DialOptions{
@@ -74,9 +71,10 @@ func (c *SRTController) Connect(params *entities.RequestParams) (*astisrt.Connec
}, },
OnDisconnect: func(conn *astisrt.Connection, err error) { OnDisconnect: func(conn *astisrt.Connection, err error) {
c.l.Sugar().Fatalw("Disconnected from SRT", c.l.Sugar().Infow("Canceling SRT",
"error", err, "error", err,
) )
cancel()
}, },
Host: params.SRTHost, Host: params.SRTHost,

View File

@@ -1,7 +1,6 @@
package controllers package controllers
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@@ -28,59 +27,69 @@ func NewStreamingController(c *entities.Config, l *zap.Logger) *StreamingControl
} }
} }
func (c *StreamingController) Stream(srtConnection *astisrt.Connection, videoTrack *webrtc.TrackLocalStaticSample, metadataTrack *webrtc.DataChannel) { func (c *StreamingController) Stream(sp entities.StreamParameters) {
r, w := io.Pipe() r, w := io.Pipe()
defer r.Close() defer r.Close()
defer w.Close() defer w.Close()
defer srtConnection.Close() defer sp.SRTConnection.Close()
defer sp.WebRTCConn.Close()
defer sp.Cancel()
c.l.Sugar().Infow("start streaming") c.l.Sugar().Infow("start streaming")
// TODO: pick the proper transport? is it possible to get rtp instead? // TODO: pick the proper transport? is it possible to get rtp instead?
go c.readFromSRTIntoWriterPipe(srtConnection, w) go c.readFromSRTIntoWriterPipe(sp.SRTConnection, w)
dmx := astits.NewDemuxer(context.Background(), r) // reading from reader pipe into mpeg-ts demuxer
dmx := astits.NewDemuxer(sp.Ctx, r)
eia608Reader := eia608.NewEIA608Reader() eia608Reader := eia608.NewEIA608Reader()
h264PID := uint16(0) h264PID := uint16(0)
// reading from reader pipe
for { for {
d, err := dmx.NextData() select {
if err != nil { case <-sp.Ctx.Done():
c.l.Sugar().Errorw("failed to demux mpeg ts", c.l.Sugar().Errorw("stream was cancelled")
"error", err, return
) default:
break d, err := dmx.NextData()
}
if d.PMT != nil {
h264PID = c.captureMediaInfoAndSendToWebRTC(d, metadataTrack, h264PID)
c.captureBitrateAndSendToWebRTC(d, metadataTrack)
}
if d.PID == h264PID && d.PES != nil {
if err = videoTrack.WriteSample(media.Sample{Data: d.PES.Data, Duration: time.Second / 30}); err != nil {
c.l.Sugar().Errorw("failed to write a sample mpeg ts to web rtc",
"error", err,
)
break
}
captions, err := eia608Reader.Parse(d.PES)
if err != nil { if err != nil {
c.l.Sugar().Errorw("failed to parse eia 608", c.l.Sugar().Errorw("failed to demux mpeg ts",
"error", err, "error", err,
) )
break break
} }
if captions != "" {
captionsMsg, err := eia608.BuildCaptionsMessage(d.PES.Header.OptionalHeader.PTS, captions) if d.PMT != nil {
if err != nil { h264PID = c.captureMediaInfoAndSendToWebRTC(d, sp.MetadataTrack, h264PID)
c.l.Sugar().Errorw("failed to build captions message", c.captureBitrateAndSendToWebRTC(d, sp.MetadataTrack)
}
if d.PID == h264PID && d.PES != nil {
// writing video from mpeg-ts into webrtc
if err = sp.VideoTrack.WriteSample(media.Sample{Data: d.PES.Data, Duration: time.Second / 30}); err != nil {
c.l.Sugar().Errorw("failed to write a sample mpeg ts to web rtc",
"error", err, "error", err,
) )
break break
} }
metadataTrack.SendText(captionsMsg) captions, err := eia608Reader.Parse(d.PES)
if err != nil {
c.l.Sugar().Errorw("failed to parse eia 608",
"error", err,
)
break
}
if captions != "" {
captionsMsg, err := eia608.BuildCaptionsMessage(d.PES.Header.OptionalHeader.PTS, captions)
if err != nil {
c.l.Sugar().Errorw("failed to build captions message",
"error", err,
)
break
}
sp.MetadataTrack.SendText(captionsMsg)
}
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package controllers package controllers
import ( import (
"context"
"net" "net"
"github.com/flavioribeiro/donut/internal/entities" "github.com/flavioribeiro/donut/internal/entities"
@@ -27,7 +28,7 @@ func NewWebRTCController(
} }
} }
func (c *WebRTCController) CreatePeerConnection() (*webrtc.PeerConnection, error) { func (c *WebRTCController) CreatePeerConnection(cancel context.CancelFunc) (*webrtc.PeerConnection, error) {
c.l.Sugar().Infow("trying to set up web rtc conn") c.l.Sugar().Infow("trying to set up web rtc conn")
peerConnectionConfiguration := webrtc.Configuration{} peerConnectionConfiguration := webrtc.Configuration{}
@@ -48,6 +49,18 @@ func (c *WebRTCController) CreatePeerConnection() (*webrtc.PeerConnection, error
} }
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) { peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
finished := connectionState == webrtc.ICEConnectionStateClosed ||
connectionState == webrtc.ICEConnectionStateDisconnected ||
connectionState == webrtc.ICEConnectionStateCompleted ||
connectionState == webrtc.ICEConnectionStateFailed
if finished {
c.l.Sugar().Infow("Canceling webrtc",
"status", connectionState.String(),
)
cancel()
}
c.l.Sugar().Infow("OnICEConnectionStateChange", c.l.Sugar().Infow("OnICEConnectionStateChange",
"status", connectionState.String(), "status", connectionState.String(),
) )

View File

@@ -1,8 +1,10 @@
package entities package entities
import ( import (
"context"
"fmt" "fmt"
astisrt "github.com/asticode/go-astisrt/pkg"
"github.com/pion/webrtc/v3" "github.com/pion/webrtc/v3"
) )
@@ -67,6 +69,15 @@ type Track struct {
Type TrackType Type TrackType
} }
type StreamParameters struct {
WebRTCConn *webrtc.PeerConnection
Cancel context.CancelFunc
Ctx context.Context
SRTConnection *astisrt.Connection
VideoTrack *webrtc.TrackLocalStaticSample
MetadataTrack *webrtc.DataChannel
}
type Config struct { type Config struct {
HTTPPort int32 `required:"true" default:"8080"` HTTPPort int32 `required:"true" default:"8080"`
HTTPHost string `required:"true" default:"0.0.0.0"` HTTPHost string `required:"true" default:"0.0.0.0"`

View File

@@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
@@ -53,7 +54,9 @@ func (h *SignalingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) err
return err return err
} }
peer, err := h.webRTCController.CreatePeerConnection() ctx, cancel := context.WithCancel(context.Background())
peer, err := h.webRTCController.CreatePeerConnection(cancel)
if err != nil { if err != nil {
h.l.Sugar().Errorw("error while setting up web rtc connection", h.l.Sugar().Errorw("error while setting up web rtc connection",
"error", err, "error", err,
@@ -99,7 +102,7 @@ func (h *SignalingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) err
return err return err
} }
srtConnection, err := h.srtController.Connect(&params) srtConnection, err := h.srtController.Connect(cancel, params)
if err != nil { if err != nil {
h.l.Sugar().Errorw("error while connecting to an srt server", h.l.Sugar().Errorw("error while connecting to an srt server",
"error", err, "error", err,
@@ -107,7 +110,14 @@ func (h *SignalingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) err
return err return err
} }
go h.streamingController.Stream(srtConnection, videoTrack, metadataSender) go h.streamingController.Stream(entities.StreamParameters{
Cancel: cancel,
Ctx: ctx,
WebRTCConn: peer,
SRTConnection: srtConnection,
VideoTrack: videoTrack,
MetadataTrack: metadataSender,
})
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)