mediamtx/internal/staticsources/webrtc/source.go
Alessandro Ros 0cdae40fe3
estimate absolute timestamp more precisely (#5078)
When the absolute timestamp of incoming frames was not available, it
was filled with the current timestamp, which is influenced by latency
over time.

This mechanism is replaced by an algorithm that detects when latency is
the lowest, stores the current timestamp and uses it as reference
throughout the rest of the stream.
2025-10-12 11:02:14 +02:00

120 lines
2.7 KiB
Go

// Package webrtc contains the WebRTC static source.
package webrtc
import (
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/bluenviron/gortsplib/v5/pkg/description"
"github.com/bluenviron/mediamtx/internal/conf"
"github.com/bluenviron/mediamtx/internal/defs"
"github.com/bluenviron/mediamtx/internal/logger"
"github.com/bluenviron/mediamtx/internal/protocols/tls"
"github.com/bluenviron/mediamtx/internal/protocols/webrtc"
"github.com/bluenviron/mediamtx/internal/protocols/whip"
"github.com/bluenviron/mediamtx/internal/stream"
)
type parent interface {
logger.Writer
SetReady(req defs.PathSourceStaticSetReadyReq) defs.PathSourceStaticSetReadyRes
SetNotReady(req defs.PathSourceStaticSetNotReadyReq)
}
// Source is a WebRTC static source.
type Source struct {
ReadTimeout conf.Duration
Parent parent
}
// Log implements logger.Writer.
func (s *Source) Log(level logger.Level, format string, args ...interface{}) {
s.Parent.Log(level, "[WebRTC source] "+format, args...)
}
// Run implements StaticSource.
func (s *Source) Run(params defs.StaticSourceRunParams) error {
s.Log(logger.Debug, "connecting")
u, err := url.Parse(params.ResolvedSource)
if err != nil {
return err
}
u.Scheme = strings.ReplaceAll(u.Scheme, "whep", "http")
tr := &http.Transport{
TLSClientConfig: tls.MakeConfig(u.Hostname(), params.Conf.SourceFingerprint),
}
defer tr.CloseIdleConnections()
client := whip.Client{
URL: u,
HTTPClient: &http.Client{
Timeout: time.Duration(s.ReadTimeout),
Transport: tr,
},
Log: s,
}
err = client.Initialize(params.Context)
if err != nil {
return err
}
var stream *stream.Stream
medias, err := webrtc.ToStream(client.PeerConnection(), params.Conf, &stream, s)
if err != nil {
client.Close() //nolint:errcheck
return err
}
rres := s.Parent.SetReady(defs.PathSourceStaticSetReadyReq{
Desc: &description.Session{Medias: medias},
GenerateRTPPackets: true,
FillNTP: !params.Conf.UseAbsoluteTimestamp,
})
if rres.Err != nil {
client.Close() //nolint:errcheck
return rres.Err
}
defer s.Parent.SetNotReady(defs.PathSourceStaticSetNotReadyReq{})
stream = rres.Stream
client.StartReading()
readErr := make(chan error)
go func() {
readErr <- client.Wait()
}()
for {
select {
case err = <-readErr:
client.Close() //nolint:errcheck
return err
case <-params.ReloadConf:
case <-params.Context.Done():
client.Close() //nolint:errcheck
<-readErr
return fmt.Errorf("terminated")
}
}
}
// APISourceDescribe implements StaticSource.
func (*Source) APISourceDescribe() defs.APIPathSourceOrReader {
return defs.APIPathSourceOrReader{
Type: "webRTCSource",
ID: "",
}
}