diff --git a/FAQ.md b/FAQ.md index f513f20..d94658f 100644 --- a/FAQ.md +++ b/FAQ.md @@ -1,5 +1,18 @@ # 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. [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) diff --git a/internal/controllers/srt_controller.go b/internal/controllers/srt_controller.go index 85bdd57..bc88d1a 100644 --- a/internal/controllers/srt_controller.go +++ b/internal/controllers/srt_controller.go @@ -51,18 +51,15 @@ func NewSRTController(c *entities.Config, l *zap.Logger, lc fx.Lifecycle) (*SRTC }, 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") - if params == nil { - return nil, entities.ErrMissingRemoteOffer - } if err := params.Valid(); err != nil { return nil, err } c.l.Sugar().Infow("Connecting to SRT ", - "offer", params, + "offer", params.String(), ) 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) { - c.l.Sugar().Fatalw("Disconnected from SRT", + c.l.Sugar().Infow("Canceling SRT", "error", err, ) + cancel() }, Host: params.SRTHost, diff --git a/internal/controllers/streaming_controller.go b/internal/controllers/streaming_controller.go index 49c7a39..4a159e5 100644 --- a/internal/controllers/streaming_controller.go +++ b/internal/controllers/streaming_controller.go @@ -1,7 +1,6 @@ package controllers import ( - "context" "encoding/json" "fmt" "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() + defer r.Close() defer w.Close() - defer srtConnection.Close() + defer sp.SRTConnection.Close() + defer sp.WebRTCConn.Close() + defer sp.Cancel() c.l.Sugar().Infow("start streaming") // 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() h264PID := uint16(0) - // reading from reader pipe for { - d, err := dmx.NextData() - if err != nil { - c.l.Sugar().Errorw("failed to demux mpeg ts", - "error", err, - ) - break - } - - 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) + select { + case <-sp.Ctx.Done(): + c.l.Sugar().Errorw("stream was cancelled") + return + default: + d, err := dmx.NextData() if err != nil { - c.l.Sugar().Errorw("failed to parse eia 608", + c.l.Sugar().Errorw("failed to demux mpeg ts", "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", + + if d.PMT != nil { + h264PID = c.captureMediaInfoAndSendToWebRTC(d, sp.MetadataTrack, h264PID) + 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, ) 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) + } } } } diff --git a/internal/controllers/webrtc_controller.go b/internal/controllers/webrtc_controller.go index dc55da5..6e24d39 100644 --- a/internal/controllers/webrtc_controller.go +++ b/internal/controllers/webrtc_controller.go @@ -1,6 +1,7 @@ package controllers import ( + "context" "net" "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") peerConnectionConfiguration := webrtc.Configuration{} @@ -48,6 +49,18 @@ func (c *WebRTCController) CreatePeerConnection() (*webrtc.PeerConnection, error } 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", "status", connectionState.String(), ) diff --git a/internal/entities/entities.go b/internal/entities/entities.go index 9ede0dd..084956a 100644 --- a/internal/entities/entities.go +++ b/internal/entities/entities.go @@ -1,8 +1,10 @@ package entities import ( + "context" "fmt" + astisrt "github.com/asticode/go-astisrt/pkg" "github.com/pion/webrtc/v3" ) @@ -67,6 +69,15 @@ type Track struct { 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 { HTTPPort int32 `required:"true" default:"8080"` HTTPHost string `required:"true" default:"0.0.0.0"` diff --git a/internal/web/handlers/signaling.go b/internal/web/handlers/signaling.go index 9cb8ed7..5967383 100644 --- a/internal/web/handlers/signaling.go +++ b/internal/web/handlers/signaling.go @@ -1,6 +1,7 @@ package handlers import ( + "context" "encoding/json" "net/http" @@ -53,7 +54,9 @@ func (h *SignalingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) err return err } - peer, err := h.webRTCController.CreatePeerConnection() + ctx, cancel := context.WithCancel(context.Background()) + + peer, err := h.webRTCController.CreatePeerConnection(cancel) if err != nil { h.l.Sugar().Errorw("error while setting up web rtc connection", "error", err, @@ -99,7 +102,7 @@ func (h *SignalingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) err return err } - srtConnection, err := h.srtController.Connect(¶ms) + srtConnection, err := h.srtController.Connect(cancel, params) if err != nil { h.l.Sugar().Errorw("error while connecting to an srt server", "error", err, @@ -107,7 +110,14 @@ func (h *SignalingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) 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.WriteHeader(http.StatusOK)