Upgrade golangci-lint, more linters

Introduces new linters, upgrade golangci-lint to version (v1.63.4)
This commit is contained in:
Joe Turki
2025-01-02 06:04:12 -06:00
parent 99dcc6b7bf
commit feeeebf251
147 changed files with 3842 additions and 2139 deletions

View File

@@ -25,17 +25,32 @@ linters-settings:
- ^os.Exit$
- ^panic$
- ^print(ln)?$
varnamelen:
max-distance: 12
min-name-length: 2
ignore-type-assert-ok: true
ignore-map-index-ok: true
ignore-chan-recv-ok: true
ignore-decls:
- i int
- n int
- w io.Writer
- r io.Reader
- b []byte
linters:
enable:
- asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers
- bidichk # Checks for dangerous unicode character sequences
- bodyclose # checks whether HTTP response body is closed successfully
- containedctx # containedctx is a linter that detects struct contained context.Context field
- contextcheck # check the function whether use a non-inherited context
- cyclop # checks function and package cyclomatic complexity
- decorder # check declaration order and count of types, constants, variables and functions
- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f())
- dupl # Tool for code clone detection
- durationcheck # check for two durations multiplied together
- err113 # Golang linter to check the errors handling expressions
- errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted.
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`.
@@ -46,18 +61,17 @@ linters:
- forcetypeassert # finds forced type assertions
- gci # Gci control golang package import order and make it always deterministic.
- gochecknoglobals # Checks that no globals are present in Go code
- gochecknoinits # Checks that no init functions are present in Go code
- gocognit # Computes and checks the cognitive complexity of functions
- goconst # Finds repeated strings that could be replaced by a constant
- gocritic # The most opinionated Go source code linter
- gocyclo # Computes and checks the cyclomatic complexity of functions
- godot # Check if comments end in a period
- godox # Tool for detection of FIXME, TODO and other comment keywords
- err113 # Golang linter to check the errors handling expressions
- gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification
- gofumpt # Gofumpt checks whether code was gofumpt-ed.
- goheader # Checks is file header matches to pattern
- goimports # Goimports does everything that gofmt does. Additionally it checks unused imports
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod.
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.
- goprintffuncname # Checks that printf-like functions are named with `f` at the end
- gosec # Inspects source code for security problems
- gosimple # Linter for Go source code that specializes in simplifying a code
@@ -65,9 +79,15 @@ linters:
- grouper # An analyzer to analyze expression groups.
- importas # Enforces consistent import aliases
- ineffassign # Detects when assignments to existing variables are not used
- lll # Reports long lines
- maintidx # maintidx measures the maintainability index of each function.
- makezero # Finds slice declarations with non-zero initial length
- misspell # Finds commonly misspelled English words in comments
- nakedret # Finds naked returns in functions greater than a specified function length
- nestif # Reports deeply nested if statements
- nilerr # Finds the code that returns nil even if it checks that the error is not nil.
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value.
- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
- noctx # noctx finds sending http request without context.Context
- predeclared # find code that shadows one of Go's predeclared identifiers
- revive # golint replacement, finds style mistakes
@@ -75,28 +95,22 @@ linters:
- stylecheck # Stylecheck is a replacement for golint
- tagliatelle # Checks the struct tags.
- tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
- typecheck # Like the front-end of a Go compiler, parses and type-checks Go code
- unconvert # Remove unnecessary type conversions
- unparam # Reports unused function parameters
- unused # Checks Go code for unused constants, variables, functions and types
- varnamelen # checks that the length of a variable's name matches its scope
- wastedassign # wastedassign finds wasted assignment statements
- whitespace # Tool for detection of leading and trailing whitespace
disable:
- depguard # Go linter that checks if package imports are in a list of acceptable packages
- containedctx # containedctx is a linter that detects struct contained context.Context field
- cyclop # checks function and package cyclomatic complexity
- funlen # Tool for detection of long functions
- gocyclo # Computes and checks the cyclomatic complexity of functions
- godot # Check if comments end in a period
- gomnd # An analyzer to detect magic numbers.
- gochecknoinits # Checks that no init functions are present in Go code
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations.
- interfacebloat # A linter that checks length of interface.
- ireturn # Accept Interfaces, Return Concrete Types
- lll # Reports long lines
- maintidx # maintidx measures the maintainability index of each function.
- makezero # Finds slice declarations with non-zero initial length
- nakedret # Finds naked returns in functions greater than a specified function length
- nestif # Reports deeply nested if statements
- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity
- mnd # An analyzer to detect magic numbers
- nolintlint # Reports ill-formed or insufficient nolint directives
- paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test
- prealloc # Finds slice declarations that could potentially be preallocated
@@ -104,8 +118,7 @@ linters:
- rowserrcheck # checks whether Err of rows is checked successfully
- sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed.
- testpackage # linter that makes you use a separate _test package
- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers
- varnamelen # checks that the length of a variable's name matches its scope
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes
- wrapcheck # Checks that errors returned from external packages are wrapped
- wsl # Whitespace Linter - Forces you to use empty lines!

24
api.go
View File

@@ -29,38 +29,38 @@ type API struct {
// It uses the default Codecs and Interceptors unless you customize them
// using WithMediaEngine and WithInterceptorRegistry respectively.
func NewAPI(options ...func(*API)) *API {
a := &API{
api := &API{
interceptor: &interceptor.NoOp{},
settingEngine: &SettingEngine{},
}
for _, o := range options {
o(a)
o(api)
}
if a.settingEngine.LoggerFactory == nil {
a.settingEngine.LoggerFactory = logging.NewDefaultLoggerFactory()
if api.settingEngine.LoggerFactory == nil {
api.settingEngine.LoggerFactory = logging.NewDefaultLoggerFactory()
}
logger := a.settingEngine.LoggerFactory.NewLogger("api")
logger := api.settingEngine.LoggerFactory.NewLogger("api")
if a.mediaEngine == nil {
a.mediaEngine = &MediaEngine{}
err := a.mediaEngine.RegisterDefaultCodecs()
if api.mediaEngine == nil {
api.mediaEngine = &MediaEngine{}
err := api.mediaEngine.RegisterDefaultCodecs()
if err != nil {
logger.Errorf("Failed to register default codecs %s", err)
}
}
if a.interceptorRegistry == nil {
a.interceptorRegistry = &interceptor.Registry{}
err := RegisterDefaultInterceptors(a.mediaEngine, a.interceptorRegistry)
if api.interceptorRegistry == nil {
api.interceptorRegistry = &interceptor.Registry{}
err := RegisterDefaultInterceptors(api.mediaEngine, api.interceptorRegistry)
if err != nil {
logger.Errorf("Failed to register default interceptors %s", err)
}
}
return a
return api
}
// WithMediaEngine allows providing a MediaEngine to the API.

View File

@@ -27,5 +27,6 @@ func (b *atomicBool) swap(value bool) bool {
if value {
i = 1
}
return atomic.SwapInt32(&(b.val), i) != 0
}

View File

@@ -14,7 +14,7 @@ import (
type BundlePolicy int
const (
// BundlePolicyUnknown is the enum's zero-value
// BundlePolicyUnknown is the enum's zero-value.
BundlePolicyUnknown BundlePolicy = iota
// BundlePolicyBalanced indicates to gather ICE candidates for each
@@ -67,7 +67,7 @@ func (t BundlePolicy) String() string {
}
}
// UnmarshalJSON parses the JSON-encoded data and stores the result
// UnmarshalJSON parses the JSON-encoded data and stores the result.
func (t *BundlePolicy) UnmarshalJSON(b []byte) error {
var val string
if err := json.Unmarshal(b, &val); err != nil {
@@ -75,10 +75,11 @@ func (t *BundlePolicy) UnmarshalJSON(b []byte) error {
}
*t = newBundlePolicy(val)
return nil
}
// MarshalJSON returns the JSON encoding
// MarshalJSON returns the JSON encoding.
func (t BundlePolicy) MarshalJSON() ([]byte, error) {
return json.Marshal(t.String())
}

View File

@@ -62,28 +62,36 @@ func NewCertificate(key crypto.PrivateKey, tpl x509.Certificate) (*Certificate,
return nil, &rtcerr.UnknownError{Err: err}
}
return &Certificate{privateKey: key, x509Cert: cert, statsID: fmt.Sprintf("certificate-%d", time.Now().UnixNano())}, nil
return &Certificate{
privateKey: key,
x509Cert: cert,
statsID: fmt.Sprintf("certificate-%d", time.Now().UnixNano()),
}, nil
}
// Equals determines if two certificates are identical by comparing both the
// secretKeys and x509Certificates.
func (c Certificate) Equals(o Certificate) bool {
func (c Certificate) Equals(cert Certificate) bool {
switch cSK := c.privateKey.(type) {
case *rsa.PrivateKey:
if oSK, ok := o.privateKey.(*rsa.PrivateKey); ok {
if oSK, ok := cert.privateKey.(*rsa.PrivateKey); ok {
if cSK.N.Cmp(oSK.N) != 0 {
return false
}
return c.x509Cert.Equal(o.x509Cert)
return c.x509Cert.Equal(cert.x509Cert)
}
return false
case *ecdsa.PrivateKey:
if oSK, ok := o.privateKey.(*ecdsa.PrivateKey); ok {
if oSK, ok := cert.privateKey.(*ecdsa.PrivateKey); ok {
if cSK.X.Cmp(oSK.X) != 0 || cSK.Y.Cmp(oSK.Y) != 0 {
return false
}
return c.x509Cert.Equal(o.x509Cert)
return c.x509Cert.Equal(cert.x509Cert)
}
return false
default:
return false
@@ -95,6 +103,7 @@ func (c Certificate) Expires() time.Time {
if c.x509Cert == nil {
return time.Time{}
}
return c.x509Cert.NotAfter
}
@@ -150,7 +159,7 @@ func GenerateCertificate(secretKey crypto.PrivateKey) (*Certificate, error) {
// CertificateFromX509 creates a new WebRTC Certificate from a given PrivateKey and Certificate
//
// This can be used if you want to share a certificate across multiple PeerConnections
// This can be used if you want to share a certificate across multiple PeerConnections.
func CertificateFromX509(privateKey crypto.PrivateKey, certificate *x509.Certificate) Certificate {
return Certificate{privateKey, certificate, fmt.Sprintf("certificate-%d", time.Now().UnixNano())}
}
@@ -176,11 +185,12 @@ func (c Certificate) collectStats(report *statsReportCollector) error {
}
report.Collect(stats.ID, stats)
return nil
}
// CertificateFromPEM creates a fresh certificate based on a string containing
// pem blocks fort the private key and x509 certificate
// pem blocks fort the private key and x509 certificate.
func CertificateFromPEM(pems string) (*Certificate, error) {
// decode & parse the certificate
block, more := pem.Decode([]byte(pems))
@@ -206,18 +216,19 @@ func CertificateFromPEM(pems string) (*Certificate, error) {
return nil, fmt.Errorf("unable to parse private key: %w", err)
}
x := CertificateFromX509(privateKey, cert)
return &x, nil
}
// PEM returns the certificate encoded as two pem block: once for the X509
// certificate and the other for the private key
// certificate and the other for the private key.
func (c Certificate) PEM() (string, error) {
// First write the X509 certificate
var o strings.Builder
var builder strings.Builder
xcertBytes := make(
[]byte, base64.StdEncoding.EncodedLen(len(c.x509Cert.Raw)))
base64.StdEncoding.Encode(xcertBytes, c.x509Cert.Raw)
err := pem.Encode(&o, &pem.Block{Type: "CERTIFICATE", Bytes: xcertBytes})
err := pem.Encode(&builder, &pem.Block{Type: "CERTIFICATE", Bytes: xcertBytes})
if err != nil {
return "", fmt.Errorf("failed to pem encode the X certificate: %w", err)
}
@@ -226,9 +237,10 @@ func (c Certificate) PEM() (string, error) {
if err != nil {
return "", fmt.Errorf("failed to marshal private key: %w", err)
}
err = pem.Encode(&o, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
err = pem.Encode(&builder, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
if err != nil {
return "", fmt.Errorf("failed to encode private key: %w", err)
}
return o.String(), nil
return builder.String(), nil
}

View File

@@ -23,5 +23,6 @@ func (c Configuration) getICEServers() []ICEServer {
iceServers[iceServersIndex].URLs[urlsIndex] = rawURL
}
}
return iceServers
}

View File

@@ -43,7 +43,7 @@ func TestConfiguration_getICEServers(t *testing.T) {
}
func TestConfigurationJSON(t *testing.T) {
j := `{
config := `{
"iceServers": [{"urls": ["turn:turn.example.org"],
"username": "jch",
"credential": "topsecret"
@@ -67,7 +67,7 @@ func TestConfigurationJSON(t *testing.T) {
}
var conf2 Configuration
assert.NoError(t, json.Unmarshal([]byte(j), &conf2))
assert.NoError(t, json.Unmarshal([]byte(config), &conf2))
assert.Equal(t, conf, conf2)
j2, err := json.Marshal(conf2)

View File

@@ -7,16 +7,16 @@ import "github.com/pion/dtls/v3"
const (
// default as the standard ethernet MTU
// can be overwritten with SettingEngine.SetReceiveMTU()
// can be overwritten with SettingEngine.SetReceiveMTU().
receiveMTU = 1500
// simulcastProbeCount is the amount of RTP Packets
// that handleUndeclaredSSRC will read and try to dispatch from
// mid and rid values
// mid and rid values.
simulcastProbeCount = 10
// simulcastMaxProbeRoutines is how many active routines can be used to probe
// If the total amount of incoming SSRCes exceeds this new requests will be ignored
// If the total amount of incoming SSRCes exceeds this new requests will be ignored.
simulcastMaxProbeRoutines = 25
mediaSectionApplication = "application"
@@ -33,14 +33,21 @@ const (
generatedCertificateOrigin = "WebRTC"
// AttributeRtxPayloadType is the interceptor attribute added when Read() returns an RTX packet containing the RTX stream payload type
// AttributeRtxPayloadType is the interceptor attribute added when Read()
// returns an RTX packet containing the RTX stream payload type.
AttributeRtxPayloadType = "rtx_payload_type"
// AttributeRtxSsrc is the interceptor attribute added when Read() returns an RTX packet containing the RTX stream SSRC
// AttributeRtxSsrc is the interceptor attribute added when Read()
// returns an RTX packet containing the RTX stream SSRC.
AttributeRtxSsrc = "rtx_ssrc"
// AttributeRtxSequenceNumber is the interceptor attribute added when Read() returns an RTX packet containing the RTX stream sequence number
// AttributeRtxSequenceNumber is the interceptor attribute added when
// Read() returns an RTX packet containing the RTX stream sequence number.
AttributeRtxSequenceNumber = "rtx_sequence_number"
)
func defaultSrtpProtectionProfiles() []dtls.SRTPProtectionProfile {
return []dtls.SRTPProtectionProfile{dtls.SRTP_AEAD_AES_256_GCM, dtls.SRTP_AEAD_AES_128_GCM, dtls.SRTP_AES128_CM_HMAC_SHA1_80}
return []dtls.SRTPProtectionProfile{
dtls.SRTP_AEAD_AES_256_GCM,
dtls.SRTP_AEAD_AES_128_GCM,
dtls.SRTP_AES128_CM_HMAC_SHA1_80,
}
}

View File

@@ -25,7 +25,7 @@ var errSCTPNotEstablished = errors.New("SCTP not established")
// DataChannel represents a WebRTC DataChannel
// The DataChannel interface represents a network channel
// which can be used for bidirectional peer-to-peer transfers of arbitrary data
// which can be used for bidirectional peer-to-peer transfers of arbitrary data.
type DataChannel struct {
mu sync.RWMutex
@@ -87,13 +87,17 @@ func (api *API) NewDataChannel(transport *SCTPTransport, params *DataChannelPara
// newDataChannel is an internal constructor for the data channel used to
// create the DataChannel object before the networking is set up.
func (api *API) newDataChannel(params *DataChannelParameters, sctpTransport *SCTPTransport, log logging.LeveledLogger) (*DataChannel, error) {
func (api *API) newDataChannel(
params *DataChannelParameters,
sctpTransport *SCTPTransport,
log logging.LeveledLogger,
) (*DataChannel, error) {
// https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #5)
if len(params.Label) > 65535 {
return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit}
}
d := &DataChannel{
dataChannel := &DataChannel{
sctpTransport: sctpTransport,
statsID: fmt.Sprintf("DataChannel-%d", time.Now().UnixNano()),
label: params.Label,
@@ -107,12 +111,13 @@ func (api *API) newDataChannel(params *DataChannelParameters, sctpTransport *SCT
log: log,
}
d.setReadyState(DataChannelStateConnecting)
return d, nil
dataChannel.setReadyState(DataChannelStateConnecting)
return dataChannel, nil
}
// open opens the datachannel over the sctp transport
func (d *DataChannel) open(sctpTransport *SCTPTransport) error {
// open opens the datachannel over the sctp transport.
func (d *DataChannel) open(sctpTransport *SCTPTransport) error { //nolint:cyclop
association := sctpTransport.association()
if association == nil {
return errSCTPNotEstablished
@@ -121,6 +126,7 @@ func (d *DataChannel) open(sctpTransport *SCTPTransport) error {
d.mu.Lock()
if d.sctpTransport != nil { // already open
d.mu.Unlock()
return nil
}
d.sctpTransport = sctpTransport
@@ -175,6 +181,7 @@ func (d *DataChannel) open(sctpTransport *SCTPTransport) error {
dc, err := datachannel.Dial(association, *d.id, cfg)
if err != nil {
d.mu.Unlock()
return err
}
@@ -185,6 +192,7 @@ func (d *DataChannel) open(sctpTransport *SCTPTransport) error {
d.onDial()
d.handleOpen(dc, false, d.negotiated)
return nil
}
@@ -197,7 +205,7 @@ func (d *DataChannel) Transport() *SCTPTransport {
}
// After onOpen is complete check that the user called detach
// and provide an error message if the call was missed
// and provide an error message if the call was missed.
func (d *DataChannel) checkDetachAfterOpen() {
d.mu.RLock()
defer d.mu.RUnlock()
@@ -229,6 +237,7 @@ func (d *DataChannel) onOpen() {
handler := d.onOpenHandler
if d.isGracefulClosed {
d.mu.RUnlock()
return
}
d.mu.RUnlock()
@@ -242,7 +251,7 @@ func (d *DataChannel) onOpen() {
}
// OnDial sets an event handler which is invoked when the
// peer has been dialed, but before said peer has responded
// peer has been dialed, but before said peer has responded.
func (d *DataChannel) OnDial(f func()) {
d.mu.Lock()
d.dialHandlerOnce = sync.Once{}
@@ -260,6 +269,7 @@ func (d *DataChannel) onDial() {
handler := d.onDialHandler
if d.isGracefulClosed {
d.mu.RUnlock()
return
}
d.mu.RUnlock()
@@ -308,6 +318,7 @@ func (d *DataChannel) onMessage(msg DataChannelMessage) {
handler := d.onMessageHandler
if d.isGracefulClosed {
d.mu.RUnlock()
return
}
d.mu.RUnlock()
@@ -376,6 +387,7 @@ func (d *DataChannel) onError(err error) {
handler := d.onErrorHandler
if d.isGracefulClosed {
d.mu.RUnlock()
return
}
d.mu.RUnlock()
@@ -401,18 +413,19 @@ func (d *DataChannel) readLoop() {
d.onError(err)
}
d.onClose()
return
}
m := DataChannelMessage{Data: make([]byte, n), IsString: isString}
copy(m.Data, buffer[:n])
msg := DataChannelMessage{Data: make([]byte, n), IsString: isString}
copy(msg.Data, buffer[:n])
// NB: Why was DataChannelMessage not passed as a pointer value?
d.onMessage(m) // nolint:staticcheck
d.onMessage(msg) // nolint:staticcheck
}
}
// Send sends the binary message to the DataChannel peer
// Send sends the binary message to the DataChannel peer.
func (d *DataChannel) Send(data []byte) error {
err := d.ensureOpen()
if err != nil {
@@ -420,10 +433,11 @@ func (d *DataChannel) Send(data []byte) error {
}
_, err = d.dataChannel.WriteDataChannel(data, false)
return err
}
// SendText sends the text message to the DataChannel peer
// SendText sends the text message to the DataChannel peer.
func (d *DataChannel) SendText(s string) error {
err := d.ensureOpen()
if err != nil {
@@ -431,6 +445,7 @@ func (d *DataChannel) SendText(s string) error {
}
_, err = d.dataChannel.WriteDataChannel([]byte(s), true)
return err
}
@@ -440,6 +455,7 @@ func (d *DataChannel) ensureOpen() error {
if d.ReadyState() != DataChannelStateOpen {
return io.ErrClosedPipe
}
return nil
}
@@ -465,11 +481,13 @@ func (d *DataChannel) DetachWithDeadline() (datachannel.ReadWriteCloserDeadliner
if !d.api.settingEngine.detach.DataChannels {
d.mu.Unlock()
return nil, errDetachNotEnabled
}
if d.dataChannel == nil {
d.mu.Unlock()
return nil, errDetachBeforeOpened
}
@@ -616,6 +634,7 @@ func (d *DataChannel) ReadyState() DataChannelState {
if v, ok := d.readyState.Load().(DataChannelState); ok {
return v
}
return DataChannelState(0)
}
@@ -636,6 +655,7 @@ func (d *DataChannel) BufferedAmount() uint64 {
if d.dataChannel == nil {
return 0
}
return d.dataChannel.BufferedAmount()
}
@@ -652,6 +672,7 @@ func (d *DataChannel) BufferedAmountLowThreshold() uint64 {
if d.dataChannel == nil {
return d.bufferedAmountLowThreshold
}
return d.dataChannel.BufferedAmountLowThreshold()
}
@@ -684,6 +705,7 @@ func (d *DataChannel) OnBufferedAmountLow(f func()) {
func (d *DataChannel) getStatsID() string {
d.mu.Lock()
defer d.mu.Unlock()
return d.statsID
}

View File

@@ -180,7 +180,7 @@ func TestDataChannelParamters_Go(t *testing.T) {
})
}
func TestDataChannelBufferedAmount(t *testing.T) {
func TestDataChannelBufferedAmount(t *testing.T) { //nolint:cyclop
t.Run("set before datachannel becomes open", func(t *testing.T) {
report := test.CheckRoutines(t)
defer report()
@@ -301,14 +301,14 @@ func TestDataChannelBufferedAmount(t *testing.T) {
done := make(chan bool)
answerPC.OnDataChannel(func(d *DataChannel) {
answerPC.OnDataChannel(func(dataChannel *DataChannel) {
// Make sure this is the data channel we were looking for. (Not the one
// created in signalPair).
if d.Label() != expectedLabel {
if dataChannel.Label() != expectedLabel {
return
}
var nPacketsReceived int
d.OnMessage(func(DataChannelMessage) {
dataChannel.OnMessage(func(DataChannelMessage) {
nPacketsReceived++
if nPacketsReceived == 10 {
@@ -318,7 +318,7 @@ func TestDataChannelBufferedAmount(t *testing.T) {
}()
}
})
assert.True(t, d.Ordered(), "Ordered should be set to true")
assert.True(t, dataChannel.Ordered(), "Ordered should be set to true")
})
dc, err := offerPC.CreateDataChannel(expectedLabel, nil)
@@ -360,7 +360,9 @@ func TestDataChannelBufferedAmount(t *testing.T) {
})
}
func TestEOF(t *testing.T) {
func TestEOF(t *testing.T) { //nolint:cyclop
t.Helper()
report := test.CheckRoutines(t)
defer report()
@@ -566,7 +568,7 @@ func TestEOF(t *testing.T) {
}
// Assert that a Session Description that doesn't follow
// draft-ietf-mmusic-sctp-sdp is still accepted
// draft-ietf-mmusic-sctp-sdp is still accepted.
func TestDataChannel_NonStandardSessionDescription(t *testing.T) {
to := test.TimeOut(time.Second * 20)
defer to.Stop()

View File

@@ -19,22 +19,26 @@ import (
// bindings this is a requirement).
const expectedLabel = "data"
func closePairNow(t testing.TB, pc1, pc2 io.Closer) {
func closePairNow(tb testing.TB, pc1, pc2 io.Closer) {
tb.Helper()
var fail bool
if err := pc1.Close(); err != nil {
t.Errorf("Failed to close PeerConnection: %v", err)
tb.Errorf("Failed to close PeerConnection: %v", err)
fail = true
}
if err := pc2.Close(); err != nil {
t.Errorf("Failed to close PeerConnection: %v", err)
tb.Errorf("Failed to close PeerConnection: %v", err)
fail = true
}
if fail {
t.FailNow()
tb.FailNow()
}
}
func closePair(t *testing.T, pc1, pc2 io.Closer, done <-chan bool) {
t.Helper()
select {
case <-time.After(10 * time.Second):
t.Fatalf("closePair timed out waiting for done signal")
@@ -43,7 +47,12 @@ func closePair(t *testing.T, pc1, pc2 io.Closer, done <-chan bool) {
}
}
func setUpDataChannelParametersTest(t *testing.T, options *DataChannelInit) (*PeerConnection, *PeerConnection, *DataChannel, chan bool) {
func setUpDataChannelParametersTest(
t *testing.T,
options *DataChannelInit,
) (*PeerConnection, *PeerConnection, *DataChannel, chan bool) {
t.Helper()
offerPC, answerPC, err := newPair()
if err != nil {
t.Fatalf("Failed to create a PC pair for testing")
@@ -59,6 +68,8 @@ func setUpDataChannelParametersTest(t *testing.T, options *DataChannelInit) (*Pe
}
func closeReliabilityParamTest(t *testing.T, pc1, pc2 *PeerConnection, done chan bool) {
t.Helper()
err := signalPair(pc1, pc2)
if err != nil {
t.Fatalf("Failed to signal our PC pair for testing")
@@ -75,6 +86,8 @@ func BenchmarkDataChannelSend32(b *testing.B) { benchmarkDataChannelSend(b, 32)
// See https://github.com/pion/webrtc/issues/1516
func benchmarkDataChannelSend(b *testing.B, numChannels int) {
b.Helper()
offerPC, answerPC, err := newPair()
if err != nil {
b.Fatalf("Failed to create a PC pair for testing")
@@ -219,7 +232,7 @@ func TestDataChannel_Open(t *testing.T) {
})
}
func TestDataChannel_Send(t *testing.T) {
func TestDataChannel_Send(t *testing.T) { //nolint:cyclop
t.Run("before signaling", func(t *testing.T) {
report := test.CheckRoutines(t)
defer report()
@@ -362,7 +375,7 @@ func TestDataChannel_Close(t *testing.T) {
})
}
func TestDataChannelParameters(t *testing.T) {
func TestDataChannelParameters(t *testing.T) { //nolint:cyclop
report := test.CheckRoutines(t)
defer report()

View File

@@ -7,7 +7,7 @@ package webrtc
type DataChannelState int
const (
// DataChannelStateUnknown is the enum's zero-value
// DataChannelStateUnknown is the enum's zero-value.
DataChannelStateUnknown DataChannelState = iota
// DataChannelStateConnecting indicates that the data channel is being
@@ -66,13 +66,14 @@ func (t DataChannelState) String() string {
}
}
// MarshalText implements encoding.TextMarshaler
// MarshalText implements encoding.TextMarshaler.
func (t DataChannelState) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler
// UnmarshalText implements encoding.TextUnmarshaler.
func (t *DataChannelState) UnmarshalText(b []byte) error {
*t = newDataChannelState(string(b))
return nil
}

View File

@@ -11,7 +11,7 @@ import (
type DTLSRole byte
const (
// DTLSRoleUnknown is the enum's zero-value
// DTLSRoleUnknown is the enum's zero-value.
DTLSRoleUnknown DTLSRole = iota
// DTLSRoleAuto defines the DTLS role is determined based on
@@ -60,7 +60,7 @@ func (r DTLSRole) String() string {
// Iterate a SessionDescription from a remote to determine if an explicit
// role can been determined from it. The decision is made from the first role we we parse.
// If no role can be found we return DTLSRoleAuto
// If no role can be found we return DTLSRoleAuto.
func dtlsRoleFromRemoteSDP(sessionDescription *sdp.SessionDescription) DTLSRole {
if sessionDescription == nil {
return DTLSRoleAuto

View File

@@ -37,6 +37,7 @@ func TestDTLSRoleFromRemoteSDP(t *testing.T) {
if err := parsed.Unmarshal([]byte(raw)); err != nil {
panic(err)
}
return parsed
}

View File

@@ -69,7 +69,7 @@ type simulcastStreamPair struct {
// This constructor is part of the ORTC API. It is not
// meant to be used together with the basic WebRTC API.
func (api *API) NewDTLSTransport(transport *ICETransport, certificates []Certificate) (*DTLSTransport, error) {
t := &DTLSTransport{
trans := &DTLSTransport{
iceTransport: transport,
api: api,
state: DTLSTransportStateNew,
@@ -84,7 +84,7 @@ func (api *API) NewDTLSTransport(transport *ICETransport, certificates []Certifi
if !x509Cert.Expires().IsZero() && now.After(x509Cert.Expires()) {
return nil, &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}
}
t.certificates = append(t.certificates, x509Cert)
trans.certificates = append(trans.certificates, x509Cert)
}
} else {
sk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@@ -95,21 +95,22 @@ func (api *API) NewDTLSTransport(transport *ICETransport, certificates []Certifi
if err != nil {
return nil, err
}
t.certificates = []Certificate{*certificate}
trans.certificates = []Certificate{*certificate}
}
return t, nil
return trans, nil
}
// ICETransport returns the currently-configured *ICETransport or nil
// if one has not been configured
// if one has not been configured.
func (t *DTLSTransport) ICETransport() *ICETransport {
t.lock.RLock()
defer t.lock.RUnlock()
return t.iceTransport
}
// onStateChange requires the caller holds the lock
// onStateChange requires the caller holds the lock.
func (t *DTLSTransport) onStateChange(state DTLSTransportState) {
t.state = state
handler := t.onStateChangeHandler
@@ -130,6 +131,7 @@ func (t *DTLSTransport) OnStateChange(f func(DTLSTransportState)) {
func (t *DTLSTransport) State() DTLSTransportState {
t.lock.RLock()
defer t.lock.RUnlock()
return t.state
}
@@ -175,10 +177,11 @@ func (t *DTLSTransport) GetLocalParameters() (DTLSParameters, error) {
}
// GetRemoteCertificate returns the certificate chain in use by the remote side
// returns an empty list prior to selection of the remote certificate
// returns an empty list prior to selection of the remote certificate.
func (t *DTLSTransport) GetRemoteCertificate() []byte {
t.lock.RLock()
defer t.lock.RUnlock()
return t.remoteCertificate
}
@@ -243,6 +246,7 @@ func (t *DTLSTransport) startSRTP() error {
t.srtpSession.Store(srtpSession)
t.srtcpSession.Store(srtcpSession)
close(t.srtpReady)
return nil
}
@@ -285,11 +289,12 @@ func (t *DTLSTransport) role() DTLSRole {
if t.iceTransport.Role() == ICERoleControlling {
return DTLSRoleServer
}
return defaultDtlsRoleAnswer
}
// Start DTLS transport negotiation with the parameters of the remote DTLS transport
func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { //nolint: gocognit
// Start DTLS transport negotiation with the parameters of the remote DTLS transport.
func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { //nolint:gocognit,cyclop
// Take lock and prepare connection, we must not hold the lock
// when connecting
prepareTransport := func() (DTLSRole, *dtls.Config, error) {
@@ -341,7 +346,7 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { //nolint:
}
if t.api.settingEngine.replayProtection.DTLS != nil {
dtlsConfig.ReplayProtectionWindow = int(*t.api.settingEngine.replayProtection.DTLS)
dtlsConfig.ReplayProtectionWindow = int(*t.api.settingEngine.replayProtection.DTLS) //nolint:gosec // G115
}
if t.api.settingEngine.dtls.clientAuth != nil {
@@ -382,12 +387,14 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { //nolint:
if err != nil {
t.onStateChange(DTLSTransportStateFailed)
return err
}
srtpProfile, ok := dtlsConn.SelectedSRTPProtectionProfile()
if !ok {
t.onStateChange(DTLSTransportStateFailed)
return ErrNoSRTPProtectionProfile
}
@@ -402,6 +409,7 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { //nolint:
t.srtpProtectionProfile = srtp.ProtectionProfileNullHmacSha1_80
default:
t.onStateChange(DTLSTransportStateFailed)
return ErrNoSRTPProtectionProfile
}
@@ -409,16 +417,18 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { //nolint:
connectionState, ok := dtlsConn.ConnectionState()
if !ok {
t.onStateChange(DTLSTransportStateFailed)
return errNoRemoteCertificate
}
if len(connectionState.PeerCertificates) == 0 {
t.onStateChange(DTLSTransportStateFailed)
return errNoRemoteCertificate
}
t.remoteCertificate = connectionState.PeerCertificates[0]
if !t.api.settingEngine.disableCertificateFingerprintVerification {
if !t.api.settingEngine.disableCertificateFingerprintVerification { //nolint:nestif
parsedRemoteCert, err := x509.ParseCertificate(t.remoteCertificate)
if err != nil {
if closeErr := dtlsConn.Close(); closeErr != nil {
@@ -426,6 +436,7 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { //nolint:
}
t.onStateChange(DTLSTransportStateFailed)
return err
}
@@ -435,6 +446,7 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { //nolint:
}
t.onStateChange(DTLSTransportStateFailed)
return err
}
}
@@ -473,6 +485,7 @@ func (t *DTLSTransport) Stop() error {
}
}
t.onStateChange(DTLSTransportStateClosed)
return util.FlattenErrs(closeErrs)
}
@@ -504,14 +517,20 @@ func (t *DTLSTransport) ensureICEConn() error {
return nil
}
func (t *DTLSTransport) storeSimulcastStream(srtpReadStream *srtp.ReadStreamSRTP, srtcpReadStream *srtp.ReadStreamSRTCP) {
func (t *DTLSTransport) storeSimulcastStream(
srtpReadStream *srtp.ReadStreamSRTP,
srtcpReadStream *srtp.ReadStreamSRTCP,
) {
t.lock.Lock()
defer t.lock.Unlock()
t.simulcastStreams = append(t.simulcastStreams, simulcastStreamPair{srtpReadStream, srtcpReadStream})
}
func (t *DTLSTransport) streamsForSSRC(ssrc SSRC, streamInfo interceptor.StreamInfo) (*srtp.ReadStreamSRTP, interceptor.RTPReader, *srtp.ReadStreamSRTCP, interceptor.RTCPReader, error) {
func (t *DTLSTransport) streamsForSSRC(
ssrc SSRC,
streamInfo interceptor.StreamInfo,
) (*srtp.ReadStreamSRTP, interceptor.RTPReader, *srtp.ReadStreamSRTCP, interceptor.RTCPReader, error) {
srtpSession, err := t.getSRTPSession()
if err != nil {
return nil, nil, nil, nil, err
@@ -522,10 +541,16 @@ func (t *DTLSTransport) streamsForSSRC(ssrc SSRC, streamInfo interceptor.StreamI
return nil, nil, nil, nil, err
}
rtpInterceptor := t.api.interceptor.BindRemoteStream(&streamInfo, interceptor.RTPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) {
n, err = rtpReadStream.Read(in)
return n, a, err
}))
rtpInterceptor := t.api.interceptor.BindRemoteStream(
&streamInfo,
interceptor.RTPReaderFunc(
func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) {
n, err = rtpReadStream.Read(in)
return n, a, err
},
),
)
srtcpSession, err := t.getSRTCPSession()
if err != nil {
@@ -537,10 +562,13 @@ func (t *DTLSTransport) streamsForSSRC(ssrc SSRC, streamInfo interceptor.StreamI
return nil, nil, nil, nil, err
}
rtcpInterceptor := t.api.interceptor.BindRTCPReader(interceptor.RTCPReaderFunc(func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) {
n, err = rtcpReadStream.Read(in)
return n, a, err
}))
rtcpInterceptor := t.api.interceptor.BindRTCPReader(interceptor.RTCPReaderFunc(
func(in []byte, a interceptor.Attributes) (n int, attributes interceptor.Attributes, err error) {
n, err = rtcpReadStream.Read(in)
return n, a, err
}),
)
return rtpReadStream, rtpInterceptor, rtcpReadStream, rtcpInterceptor, nil
}

View File

@@ -15,8 +15,8 @@ import (
"github.com/stretchr/testify/assert"
)
// An invalid fingerprint MUST cause PeerConnectionState to go to PeerConnectionStateFailed
func TestInvalidFingerprintCausesFailed(t *testing.T) {
// An invalid fingerprint MUST cause PeerConnectionState to go to PeerConnectionStateFailed.
func TestInvalidFingerprintCausesFailed(t *testing.T) { //nolint:cyclop
lim := test.TimeOut(time.Second * 5)
defer lim.Stop()
@@ -64,7 +64,10 @@ func TestInvalidFingerprintCausesFailed(t *testing.T) {
case offer := <-offerChan:
// Replace with invalid fingerprint
re := regexp.MustCompile(`sha-256 (.*?)\r`)
offer.SDP = re.ReplaceAllString(offer.SDP, "sha-256 AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA\r")
offer.SDP = re.ReplaceAllString(
offer.SDP,
"sha-256 AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA\r",
)
if err := pcAnswer.SetRemoteDescription(offer); err != nil {
t.Fatal(err)
@@ -79,7 +82,10 @@ func TestInvalidFingerprintCausesFailed(t *testing.T) {
t.Fatal(err)
}
answer.SDP = re.ReplaceAllString(answer.SDP, "sha-256 AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA\r")
answer.SDP = re.ReplaceAllString(
answer.SDP,
"sha-256 AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA:AA\r",
)
err = pcOffer.SetRemoteDescription(answer)
if err != nil {
@@ -92,12 +98,14 @@ func TestInvalidFingerprintCausesFailed(t *testing.T) {
offerConnectionHasClosed.Wait()
answerConnectionHasClosed.Wait()
if pcOffer.SCTP().Transport().State() != DTLSTransportStateClosed && pcOffer.SCTP().Transport().State() != DTLSTransportStateFailed {
if pcOffer.SCTP().Transport().State() != DTLSTransportStateClosed &&
pcOffer.SCTP().Transport().State() != DTLSTransportStateFailed {
t.Fail()
}
assert.Nil(t, pcOffer.SCTP().Transport().conn)
if pcAnswer.SCTP().Transport().State() != DTLSTransportStateClosed && pcAnswer.SCTP().Transport().State() != DTLSTransportStateFailed {
if pcAnswer.SCTP().Transport().State() != DTLSTransportStateClosed &&
pcAnswer.SCTP().Transport().State() != DTLSTransportStateFailed {
t.Fail()
}
assert.Nil(t, pcAnswer.SCTP().Transport().conn)

View File

@@ -7,7 +7,7 @@ package webrtc
type DTLSTransportState int
const (
// DTLSTransportStateUnknown is the enum's zero-value
// DTLSTransportStateUnknown is the enum's zero-value.
DTLSTransportStateUnknown DTLSTransportState = iota
// DTLSTransportStateNew indicates that DTLS has not started negotiating
@@ -76,13 +76,14 @@ func (t DTLSTransportState) String() string {
}
}
// MarshalText implements encoding.TextMarshaler
// MarshalText implements encoding.TextMarshaler.
func (t DTLSTransportState) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler
// UnmarshalText implements encoding.TextUnmarshaler.
func (t *DTLSTransportState) UnmarshalText(b []byte) error {
*t = newDTLSTransportState(string(b))
return nil
}

144
errors.go
View File

@@ -76,88 +76,100 @@ var (
// and is mutually exclusive.
ErrRetransmitsOrPacketLifeTime = errors.New("both MaxPacketLifeTime and MaxRetransmits was set")
// ErrCodecNotFound is returned when a codec search to the Media Engine fails
// ErrCodecNotFound is returned when a codec search to the Media Engine fails.
ErrCodecNotFound = errors.New("codec not found")
// ErrNoRemoteDescription indicates that an operation was rejected because
// the remote description is not set
// the remote description is not set.
ErrNoRemoteDescription = errors.New("remote description is not set")
// ErrIncorrectSDPSemantics indicates that the PeerConnection was configured to
// generate SDP Answers with different SDP Semantics than the received Offer
// generate SDP Answers with different SDP Semantics than the received Offer.
ErrIncorrectSDPSemantics = errors.New("remote SessionDescription semantics does not match configuration")
// ErrIncorrectSignalingState indicates that the signaling state of PeerConnection is not correct
// ErrIncorrectSignalingState indicates that the signaling state of PeerConnection is not correct.
ErrIncorrectSignalingState = errors.New("operation can not be run in current signaling state")
// ErrProtocolTooLarge indicates that value given for a DataChannelInit protocol is
// longer then 65535 bytes
// longer then 65535 bytes.
ErrProtocolTooLarge = errors.New("protocol is larger then 65535 bytes")
// ErrSenderNotCreatedByConnection indicates RemoveTrack was called with a RtpSender not created
// by this PeerConnection
// by this PeerConnection.
ErrSenderNotCreatedByConnection = errors.New("RtpSender not created by this PeerConnection")
// ErrSessionDescriptionNoFingerprint indicates SetRemoteDescription was called with a SessionDescription that has no
// fingerprint
// fingerprint.
ErrSessionDescriptionNoFingerprint = errors.New("SetRemoteDescription called with no fingerprint")
// ErrSessionDescriptionInvalidFingerprint indicates SetRemoteDescription was called with a SessionDescription that
// has an invalid fingerprint
// has an invalid fingerprint.
ErrSessionDescriptionInvalidFingerprint = errors.New("SetRemoteDescription called with an invalid fingerprint")
// ErrSessionDescriptionConflictingFingerprints indicates SetRemoteDescription was called with a SessionDescription that
// has an conflicting fingerprints
ErrSessionDescriptionConflictingFingerprints = errors.New("SetRemoteDescription called with multiple conflicting fingerprint")
// ErrSessionDescriptionConflictingFingerprints indicates SetRemoteDescription was called with a SessionDescription
// that has an conflicting fingerprints.
ErrSessionDescriptionConflictingFingerprints = errors.New(
"SetRemoteDescription called with multiple conflicting fingerprint",
)
// ErrSessionDescriptionMissingIceUfrag indicates SetRemoteDescription was called with a SessionDescription that
// is missing an ice-ufrag value
// is missing an ice-ufrag value.
ErrSessionDescriptionMissingIceUfrag = errors.New("SetRemoteDescription called with no ice-ufrag")
// ErrSessionDescriptionMissingIcePwd indicates SetRemoteDescription was called with a SessionDescription that
// is missing an ice-pwd value
// is missing an ice-pwd value.
ErrSessionDescriptionMissingIcePwd = errors.New("SetRemoteDescription called with no ice-pwd")
// ErrSessionDescriptionConflictingIceUfrag indicates SetRemoteDescription was called with a SessionDescription that
// contains multiple conflicting ice-ufrag values
ErrSessionDescriptionConflictingIceUfrag = errors.New("SetRemoteDescription called with multiple conflicting ice-ufrag values")
// ErrSessionDescriptionConflictingIceUfrag indicates SetRemoteDescription was called with a SessionDescription
// that contains multiple conflicting ice-ufrag values.
ErrSessionDescriptionConflictingIceUfrag = errors.New(
"SetRemoteDescription called with multiple conflicting ice-ufrag values",
)
// ErrSessionDescriptionConflictingIcePwd indicates SetRemoteDescription was called with a SessionDescription that
// contains multiple conflicting ice-pwd values
ErrSessionDescriptionConflictingIcePwd = errors.New("SetRemoteDescription called with multiple conflicting ice-pwd values")
// ErrSessionDescriptionConflictingIcePwd indicates SetRemoteDescription was called with a SessionDescription
// that contains multiple conflicting ice-pwd values.
ErrSessionDescriptionConflictingIcePwd = errors.New(
"SetRemoteDescription called with multiple conflicting ice-pwd values",
)
// ErrNoSRTPProtectionProfile indicates that the DTLS handshake completed and no SRTP Protection Profile was chosen
// ErrNoSRTPProtectionProfile indicates that the DTLS handshake completed and no SRTP Protection Profile was chosen.
ErrNoSRTPProtectionProfile = errors.New("DTLS Handshake completed and no SRTP Protection Profile was chosen")
// ErrFailedToGenerateCertificateFingerprint indicates that we failed to generate the fingerprint used for comparing certificates
// ErrFailedToGenerateCertificateFingerprint indicates that we failed to generate the fingerprint
// used for comparing certificates.
ErrFailedToGenerateCertificateFingerprint = errors.New("failed to generate certificate fingerprint")
// ErrNoCodecsAvailable indicates that operation isn't possible because the MediaEngine has no codecs available
// ErrNoCodecsAvailable indicates that operation isn't possible because the MediaEngine has no codecs available.
ErrNoCodecsAvailable = errors.New("operation failed no codecs are available")
// ErrUnsupportedCodec indicates the remote peer doesn't support the requested codec
// ErrUnsupportedCodec indicates the remote peer doesn't support the requested codec.
ErrUnsupportedCodec = errors.New("unable to start track, codec is not supported by remote")
// ErrSenderWithNoCodecs indicates that a RTPSender was created without any codecs. To send media the MediaEngine needs at
// least one configured codec.
// ErrSenderWithNoCodecs indicates that a RTPSender was created without any codecs. To send media the MediaEngine
// needs at least one configured codec.
ErrSenderWithNoCodecs = errors.New("unable to populate media section, RTPSender created with no codecs")
// ErrRTPSenderNewTrackHasIncorrectKind indicates that the new track is of a different kind than the previous/original
// ErrRTPSenderNewTrackHasIncorrectKind indicates that the new track is of a different kind than the previous/original.
ErrRTPSenderNewTrackHasIncorrectKind = errors.New("new track must be of the same kind as previous")
// ErrRTPSenderNewTrackHasIncorrectEnvelope indicates that the new track has a different envelope than the previous/original
// ErrRTPSenderNewTrackHasIncorrectEnvelope indicates that the new track has a different envelope
// than the previous/original.
ErrRTPSenderNewTrackHasIncorrectEnvelope = errors.New("new track must have the same envelope as previous")
// ErrUnbindFailed indicates that a TrackLocal was not able to be unbind
// ErrUnbindFailed indicates that a TrackLocal was not able to be unbind.
ErrUnbindFailed = errors.New("failed to unbind TrackLocal from PeerConnection")
// ErrNoPayloaderForCodec indicates that the requested codec does not have a payloader
// ErrNoPayloaderForCodec indicates that the requested codec does not have a payloader.
ErrNoPayloaderForCodec = errors.New("the requested codec does not have a payloader")
// ErrRegisterHeaderExtensionInvalidDirection indicates that a extension was registered with a direction besides `sendonly` or `recvonly`
ErrRegisterHeaderExtensionInvalidDirection = errors.New("a header extension must be registered as 'recvonly', 'sendonly' or both")
// ErrRegisterHeaderExtensionInvalidDirection indicates that a extension was
// registered with a direction besides `sendonly` or `recvonly`.
ErrRegisterHeaderExtensionInvalidDirection = errors.New(
"a header extension must be registered as 'recvonly', 'sendonly' or both",
)
// ErrSimulcastProbeOverflow indicates that too many Simulcast probe streams are in flight and the requested SSRC was ignored
// ErrSimulcastProbeOverflow indicates that too many Simulcast probe streams are in flight
// and the requested SSRC was ignored.
ErrSimulcastProbeOverflow = errors.New("simulcast probe limit has been reached, new SSRC has been discarded")
errDetachNotEnabled = errors.New("enable detaching by calling webrtc.DetachDataChannels()")
@@ -173,35 +185,49 @@ var (
errICEConnectionNotStarted = errors.New("ICE connection not started")
errICECandidateTypeUnknown = errors.New("unknown candidate type")
errICEInvalidConvertCandidateType = errors.New("cannot convert ice.CandidateType into webrtc.ICECandidateType, invalid type")
errICEAgentNotExist = errors.New("ICEAgent does not exist")
errICECandiatesCoversionFailed = errors.New("unable to convert ICE candidates to ICECandidates")
errICERoleUnknown = errors.New("unknown ICE Role")
errICEProtocolUnknown = errors.New("unknown protocol")
errICEGathererNotStarted = errors.New("gatherer not started")
errICEInvalidConvertCandidateType = errors.New(
"cannot convert ice.CandidateType into webrtc.ICECandidateType, invalid type",
)
errICEAgentNotExist = errors.New("ICEAgent does not exist")
errICECandiatesCoversionFailed = errors.New("unable to convert ICE candidates to ICECandidates")
errICERoleUnknown = errors.New("unknown ICE Role")
errICEProtocolUnknown = errors.New("unknown protocol")
errICEGathererNotStarted = errors.New("gatherer not started")
errNetworkTypeUnknown = errors.New("unknown network type")
errSDPDoesNotMatchOffer = errors.New("new sdp does not match previous offer")
errSDPDoesNotMatchAnswer = errors.New("new sdp does not match previous answer")
errPeerConnSDPTypeInvalidValue = errors.New("provided value is not a valid enum value of type SDPType")
errSDPDoesNotMatchOffer = errors.New("new sdp does not match previous offer")
errSDPDoesNotMatchAnswer = errors.New("new sdp does not match previous answer")
errPeerConnSDPTypeInvalidValue = errors.New(
"provided value is not a valid enum value of type SDPType",
)
errPeerConnStateChangeInvalid = errors.New("invalid state change op")
errPeerConnStateChangeUnhandled = errors.New("unhandled state change op")
errPeerConnSDPTypeInvalidValueSetLocalDescription = errors.New("invalid SDP type supplied to SetLocalDescription()")
errPeerConnRemoteDescriptionWithoutMidValue = errors.New("remoteDescription contained media section without mid value")
errPeerConnRemoteDescriptionNil = errors.New("remoteDescription has not been set yet")
errPeerConnSingleMediaSectionHasExplicitSSRC = errors.New("single media section has an explicit SSRC")
errPeerConnRemoteSSRCAddTransceiver = errors.New("could not add transceiver for remote SSRC")
errPeerConnSimulcastMidRTPExtensionRequired = errors.New("mid RTP Extensions required for Simulcast")
errPeerConnSimulcastStreamIDRTPExtensionRequired = errors.New("stream id RTP Extensions required for Simulcast")
errPeerConnSimulcastIncomingSSRCFailed = errors.New("incoming SSRC failed Simulcast probing")
errPeerConnAddTransceiverFromKindOnlyAcceptsOne = errors.New("AddTransceiverFromKind only accepts one RTPTransceiverInit")
errPeerConnAddTransceiverFromTrackOnlyAcceptsOne = errors.New("AddTransceiverFromTrack only accepts one RTPTransceiverInit")
errPeerConnAddTransceiverFromKindSupport = errors.New("AddTransceiverFromKind currently only supports recvonly")
errPeerConnAddTransceiverFromTrackSupport = errors.New("AddTransceiverFromTrack currently only supports sendonly and sendrecv")
errPeerConnSetIdentityProviderNotImplemented = errors.New("TODO SetIdentityProvider")
errPeerConnWriteRTCPOpenWriteStream = errors.New("WriteRTCP failed to open WriteStream")
errPeerConnTranscieverMidNil = errors.New("cannot find transceiver with mid")
errPeerConnRemoteDescriptionWithoutMidValue = errors.New(
"remoteDescription contained media section without mid value",
)
errPeerConnRemoteDescriptionNil = errors.New("remoteDescription has not been set yet")
errPeerConnSingleMediaSectionHasExplicitSSRC = errors.New("single media section has an explicit SSRC")
errPeerConnRemoteSSRCAddTransceiver = errors.New("could not add transceiver for remote SSRC")
errPeerConnSimulcastMidRTPExtensionRequired = errors.New("mid RTP Extensions required for Simulcast")
errPeerConnSimulcastStreamIDRTPExtensionRequired = errors.New("stream id RTP Extensions required for Simulcast")
errPeerConnSimulcastIncomingSSRCFailed = errors.New("incoming SSRC failed Simulcast probing")
errPeerConnAddTransceiverFromKindOnlyAcceptsOne = errors.New(
"AddTransceiverFromKind only accepts one RTPTransceiverInit",
)
errPeerConnAddTransceiverFromTrackOnlyAcceptsOne = errors.New(
"AddTransceiverFromTrack only accepts one RTPTransceiverInit",
)
errPeerConnAddTransceiverFromKindSupport = errors.New(
"AddTransceiverFromKind currently only supports recvonly",
)
errPeerConnAddTransceiverFromTrackSupport = errors.New(
"AddTransceiverFromTrack currently only supports sendonly and sendrecv",
)
errPeerConnSetIdentityProviderNotImplemented = errors.New("TODO SetIdentityProvider")
errPeerConnWriteRTCPOpenWriteStream = errors.New("WriteRTCP failed to open WriteStream")
errPeerConnTranscieverMidNil = errors.New("cannot find transceiver with mid")
errRTPReceiverDTLSTransportNil = errors.New("DTLSTransport must not be nil")
errRTPReceiverReceiveAlreadyCalled = errors.New("Receive has already been called")
@@ -227,14 +253,18 @@ var (
errSDPZeroTransceivers = errors.New("addTransceiverSDP() called with 0 transceivers")
errSDPMediaSectionMediaDataChanInvalid = errors.New("invalid Media Section. Media + DataChannel both enabled")
errSDPMediaSectionMultipleTrackInvalid = errors.New("invalid Media Section. Can not have multiple tracks in one MediaSection in UnifiedPlan")
errSDPMediaSectionMultipleTrackInvalid = errors.New(
"invalid Media Section. Can not have multiple tracks in one MediaSection in UnifiedPlan",
)
errSettingEngineSetAnsweringDTLSRole = errors.New("SetAnsweringDTLSRole must DTLSRoleClient or DTLSRoleServer")
errSignalingStateCannotRollback = errors.New("can't rollback from stable state")
errSignalingStateProposedTransitionInvalid = errors.New("invalid proposed signaling state transition")
errStatsICECandidateStateInvalid = errors.New("cannot convert to StatsICECandidatePairStateSucceeded invalid ice candidate state")
errStatsICECandidateStateInvalid = errors.New(
"cannot convert to StatsICECandidatePairStateSucceeded invalid ice candidate state",
)
errInvalidICECredentialTypeString = errors.New("invalid ICECredentialType")
errInvalidICEServer = errors.New("invalid ICEServer")

View File

@@ -39,8 +39,7 @@ const (
ivfHeaderSize = 32
)
// nolint: gocognit
func main() {
func main() { //nolint:gocognit,cyclop,maintidx
qualityLevels := []struct {
fileName string
bitrate int
@@ -58,9 +57,9 @@ func main() {
}
}
i := &interceptor.Registry{}
m := &webrtc.MediaEngine{}
if err := m.RegisterDefaultCodecs(); err != nil {
interceptorRegistry := &interceptor.Registry{}
mediaEngine := &webrtc.MediaEngine{}
if err := mediaEngine.RegisterDefaultCodecs(); err != nil {
panic(err)
}
@@ -81,17 +80,19 @@ func main() {
estimatorChan <- estimator
})
i.Add(congestionController)
if err = webrtc.ConfigureTWCCHeaderExtensionSender(m, i); err != nil {
interceptorRegistry.Add(congestionController)
if err = webrtc.ConfigureTWCCHeaderExtensionSender(mediaEngine, interceptorRegistry); err != nil {
panic(err)
}
if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil {
if err = webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil {
panic(err)
}
// Create a new RTCPeerConnection
peerConnection, err := webrtc.NewAPI(webrtc.WithInterceptorRegistry(i), webrtc.WithMediaEngine(m)).NewPeerConnection(webrtc.Configuration{
peerConnection, err := webrtc.NewAPI(
webrtc.WithInterceptorRegistry(interceptorRegistry), webrtc.WithMediaEngine(mediaEngine),
).NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
@@ -111,7 +112,9 @@ func main() {
estimator := <-estimatorChan
// Create a video track
videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion")
videoTrack, err := webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion",
)
if err != nil {
panic(err)
}
@@ -141,8 +144,8 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
})
// Wait for the offer to be pasted
@@ -193,14 +196,21 @@ func main() {
// It is important to use a time.Ticker instead of time.Sleep because
// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data
// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343)
ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
ticker := time.NewTicker(
time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000),
)
defer ticker.Stop()
frame := []byte{}
frameHeader := &ivfreader.IVFFrameHeader{}
currentTimestamp := uint64(0)
switchQualityLevel := func(newQualityLevel int) {
fmt.Printf("Switching from %s to %s \n", qualityLevels[currentQuality].fileName, qualityLevels[newQualityLevel].fileName)
fmt.Printf(
"Switching from %s to %s \n",
qualityLevels[currentQuality].fileName,
qualityLevels[newQualityLevel].fileName,
)
currentQuality = newQualityLevel
ivf.ResetReader(setReaderFile(qualityLevels[currentQuality].fileName))
for {
@@ -255,11 +265,12 @@ func setReaderFile(filename string) func(_ int64) io.Reader {
if _, err = file.Seek(ivfHeaderSize, io.SeekStart); err != nil {
panic(err)
}
return file
}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -276,10 +287,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -289,7 +301,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -22,7 +22,8 @@ import (
"github.com/pion/webrtc/v4"
)
func main() { // nolint:gocognit
// nolint:gocognit, cyclop
func main() {
port := flag.Int("port", 8080, "http server port")
flag.Parse()
@@ -41,8 +42,8 @@ func main() { // nolint:gocognit
},
}
m := &webrtc.MediaEngine{}
if err := m.RegisterDefaultCodecs(); err != nil {
mediaEngine := &webrtc.MediaEngine{}
if err := mediaEngine.RegisterDefaultCodecs(); err != nil {
panic(err)
}
@@ -50,10 +51,10 @@ func main() { // nolint:gocognit
// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
// for each PeerConnection.
i := &interceptor.Registry{}
interceptorRegistry := &interceptor.Registry{}
// Use the default set of Interceptors
if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil {
if err := webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil {
panic(err)
}
@@ -65,10 +66,13 @@ func main() { // nolint:gocognit
if err != nil {
panic(err)
}
i.Add(intervalPliFactory)
interceptorRegistry.Add(intervalPliFactory)
// Create a new RTCPeerConnection
peerConnection, err := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i)).NewPeerConnection(peerConnectionConfig)
peerConnection, err := webrtc.NewAPI(
webrtc.WithMediaEngine(mediaEngine),
webrtc.WithInterceptorRegistry(interceptorRegistry),
).NewPeerConnection(peerConnectionConfig)
if err != nil {
panic(err)
}
@@ -199,7 +203,7 @@ func main() { // nolint:gocognit
}
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -209,7 +213,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {
@@ -221,12 +225,12 @@ func decode(in string, obj *webrtc.SessionDescription) {
}
}
// httpSDPServer starts a HTTP Server that consumes SDPs
// httpSDPServer starts a HTTP Server that consumes SDPs.
func httpSDPServer(port int) chan string {
sdpChan := make(chan string)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
fmt.Fprintf(w, "done") //nolint: errcheck
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
body, _ := io.ReadAll(req.Body)
fmt.Fprintf(res, "done") //nolint: errcheck
sdpChan <- string(body)
})

View File

@@ -22,7 +22,7 @@ import (
// behavior per subsystem (ICE, DTLS, SCTP...)
type customLogger struct{}
// Print all messages except trace
// Print all messages except trace.
func (c customLogger) Trace(string) {}
func (c customLogger) Tracef(string, ...interface{}) {}
@@ -45,14 +45,16 @@ func (c customLogger) Errorf(format string, args ...interface{}) {
// customLoggerFactory satisfies the interface logging.LoggerFactory
// This allows us to create different loggers per subsystem. So we can
// add custom behavior
// add custom behavior.
type customLoggerFactory struct{}
func (c customLoggerFactory) NewLogger(subsystem string) logging.LeveledLogger {
fmt.Printf("Creating logger for %s \n", subsystem)
return customLogger{}
}
// nolint: cyclop
func main() {
// Create a new API with a custom logger
// This SettingEngine allows non-standard WebRTC behavior
@@ -90,18 +92,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
offerPeerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (offerer)\n", s.String())
offerPeerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (offerer)\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -110,11 +113,12 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
answerPeerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (answerer)\n", s.String())
answerPeerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (answerer)\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
@@ -124,9 +128,9 @@ func main() {
// Set ICE Candidate handler. As soon as a PeerConnection has gathered a candidate
// send it to the other peer
answerPeerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
if i != nil {
if iceErr := offerPeerConnection.AddICECandidate(i.ToJSON()); iceErr != nil {
answerPeerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate != nil {
if iceErr := offerPeerConnection.AddICECandidate(candidate.ToJSON()); iceErr != nil {
panic(iceErr)
}
}
@@ -134,9 +138,9 @@ func main() {
// Set ICE Candidate handler. As soon as a PeerConnection has gathered a candidate
// send it to the other peer
offerPeerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
if i != nil {
if iceErr := answerPeerConnection.AddICECandidate(i.ToJSON()); iceErr != nil {
offerPeerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate != nil {
if iceErr := answerPeerConnection.AddICECandidate(candidate.ToJSON()); iceErr != nil {
panic(iceErr)
}
}

View File

@@ -1,7 +1,10 @@
// SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
// SPDX-License-Identifier: MIT
// data-channels-detach is an example that shows how you can detach a data channel. This allows direct access the underlying [pion/datachannel](https://github.com/pion/datachannel). This allows you to interact with the data channel using a more idiomatic API based on the `io.ReadWriteCloser` interface.
// data-channels-detach is an example that shows how you can detach a data channel.
// This allows direct access the underlying [pion/datachannel](https://github.com/pion/datachannel).
// This allows you to interact with the data channel using a more idiomatic API based on
// the `io.ReadWriteCloser` interface.
package main
import (
@@ -57,18 +60,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -76,15 +80,15 @@ func main() {
})
// Register data channel creation handling
peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
fmt.Printf("New DataChannel %s %d\n", d.Label(), d.ID())
peerConnection.OnDataChannel(func(dataChannel *webrtc.DataChannel) {
fmt.Printf("New DataChannel %s %d\n", dataChannel.Label(), dataChannel.ID())
// Register channel opening handling
d.OnOpen(func() {
fmt.Printf("Data channel '%s'-'%d' open.\n", d.Label(), d.ID())
dataChannel.OnOpen(func() {
fmt.Printf("Data channel '%s'-'%d' open.\n", dataChannel.Label(), dataChannel.ID())
// Detach the data channel
raw, dErr := d.Detach()
raw, dErr := dataChannel.Detach()
if dErr != nil {
panic(dErr)
}
@@ -134,13 +138,14 @@ func main() {
select {}
}
// ReadLoop shows how to read from the datachannel directly
// ReadLoop shows how to read from the datachannel directly.
func ReadLoop(d io.Reader) {
for {
buffer := make([]byte, messageSize)
n, err := d.Read(buffer)
if err != nil {
fmt.Println("Datachannel closed; Exit the readloop:", err)
return
}
@@ -148,12 +153,14 @@ func ReadLoop(d io.Reader) {
}
}
// WriteLoop shows how to write to the datachannel directly
// WriteLoop shows how to write to the datachannel directly.
func WriteLoop(d io.Writer) {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
message, err := randutil.GenerateCryptoRandomString(messageSize, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
message, err := randutil.GenerateCryptoRandomString(
messageSize, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
)
if err != nil {
panic(err)
}
@@ -165,7 +172,7 @@ func WriteLoop(d io.Writer) {
}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -182,10 +189,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -195,7 +203,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -59,18 +59,21 @@ func createOfferer() *webrtc.PeerConnection {
sendMoreCh := make(chan struct{}, 1)
// Create a datachannel with label 'data'
dc, err := pc.CreateDataChannel("data", options)
dataChannel, err := pc.CreateDataChannel("data", options)
check(err)
// Register channel opening handling
dc.OnOpen(func() {
log.Printf("OnOpen: %s-%d. Start sending a series of 1024-byte packets as fast as it can\n", dc.Label(), dc.ID())
dataChannel.OnOpen(func() {
log.Printf(
"OnOpen: %s-%d. Start sending a series of 1024-byte packets as fast as it can\n",
dataChannel.Label(), dataChannel.ID(),
)
for {
err2 := dc.Send(buf)
err2 := dataChannel.Send(buf)
check(err2)
if dc.BufferedAmount() > maxBufferedAmount {
if dataChannel.BufferedAmount() > maxBufferedAmount {
// Wait until the bufferedAmount becomes lower than the threshold
<-sendMoreCh
}
@@ -79,10 +82,10 @@ func createOfferer() *webrtc.PeerConnection {
// Set bufferedAmountLowThreshold so that we can get notified when
// we can send more
dc.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold)
dataChannel.SetBufferedAmountLowThreshold(bufferedAmountLowThreshold)
// This callback is made when the current bufferedAmount becomes lower than the threshold
dc.OnBufferedAmountLow(func() {
dataChannel.OnBufferedAmountLow(func() {
// Make sure to not block this channel or perform long running operations in this callback
// This callback is executed by pion/sctp. If this callback is blocking it will stop operations
select {
@@ -104,12 +107,12 @@ func createAnswerer() *webrtc.PeerConnection {
pc, err := webrtc.NewPeerConnection(config)
check(err)
pc.OnDataChannel(func(dc *webrtc.DataChannel) {
pc.OnDataChannel(func(dataChannel *webrtc.DataChannel) {
var totalBytesReceived uint64
// Register channel opening handling
dc.OnOpen(func() {
log.Printf("OnOpen: %s-%d. Start receiving data", dc.Label(), dc.ID())
dataChannel.OnOpen(func() {
log.Printf("OnOpen: %s-%d. Start receiving data", dataChannel.Label(), dataChannel.ID())
since := time.Now()
// Start printing out the observed throughput
@@ -122,7 +125,7 @@ func createAnswerer() *webrtc.PeerConnection {
})
// Register the OnMessage to handle incoming messages
dc.OnMessage(func(dcMsg webrtc.DataChannelMessage) {
dataChannel.OnMessage(func(dcMsg webrtc.DataChannelMessage) {
n := len(dcMsg.Data)
atomic.AddUint64(&totalBytesReceived, uint64(n))
})
@@ -148,34 +151,35 @@ func main() {
// Set ICE Candidate handler. As soon as a PeerConnection has gathered a candidate
// send it to the other peer
answerPC.OnICECandidate(func(i *webrtc.ICECandidate) {
if i != nil {
check(offerPC.AddICECandidate(i.ToJSON()))
answerPC.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate != nil {
check(offerPC.AddICECandidate(candidate.ToJSON()))
}
})
// Set ICE Candidate handler. As soon as a PeerConnection has gathered a candidate
// send it to the other peer
offerPC.OnICECandidate(func(i *webrtc.ICECandidate) {
if i != nil {
check(answerPC.AddICECandidate(i.ToJSON()))
offerPC.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate != nil {
check(answerPC.AddICECandidate(candidate.ToJSON()))
}
})
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
offerPC.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (offerer)\n", s.String())
offerPC.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (offerer)\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -184,18 +188,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
answerPC.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (answerer)\n", s.String())
answerPC.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (answerer)\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)

View File

@@ -19,6 +19,7 @@ import (
"github.com/pion/webrtc/v4"
)
// nolint:cyclop
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
@@ -44,18 +45,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -63,12 +65,15 @@ func main() {
})
// Register data channel creation handling
peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
fmt.Printf("New DataChannel %s %d\n", d.Label(), d.ID())
peerConnection.OnDataChannel(func(dataChannel *webrtc.DataChannel) {
fmt.Printf("New DataChannel %s %d\n", dataChannel.Label(), dataChannel.ID())
// Register channel opening handling
d.OnOpen(func() {
fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", d.Label(), d.ID())
dataChannel.OnOpen(func() {
fmt.Printf(
"Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n",
dataChannel.Label(), dataChannel.ID(),
)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
@@ -80,15 +85,15 @@ func main() {
// Send the message as text
fmt.Printf("Sending '%s'\n", message)
if sendErr = d.SendText(message); sendErr != nil {
if sendErr = dataChannel.SendText(message); sendErr != nil {
panic(sendErr)
}
}
})
// Register text message handling
d.OnMessage(func(msg webrtc.DataChannelMessage) {
fmt.Printf("Message from DataChannel '%s': '%s'\n", d.Label(), string(msg.Data))
dataChannel.OnMessage(func(msg webrtc.DataChannelMessage) {
fmt.Printf("Message from DataChannel '%s': '%s'\n", dataChannel.Label(), string(msg.Data))
})
})
@@ -129,7 +134,7 @@ func main() {
select {}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -146,10 +151,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -159,7 +165,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -49,10 +49,11 @@ func serve(addr string) error {
// Serve the required pages
// DIY 'mux' to avoid additional dependencies
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
url := r.URL.Path
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
url := req.URL.Path
if url == "/wasm_exec.js" {
http.FileServer(http.Dir(filepath.Join(build.Default.GOROOT, "misc/wasm/"))).ServeHTTP(w, r)
http.FileServer(http.Dir(filepath.Join(build.Default.GOROOT, "misc/wasm/"))).ServeHTTP(res, req)
return
}
@@ -73,7 +74,11 @@ func serve(addr string) error {
}
fiddle := filepath.Join(exampleLink, "jsfiddle")
if len(parts[4]) != 0 {
http.StripPrefix("/example/"+exampleType+"/"+exampleLink+"/", http.FileServer(http.Dir(fiddle))).ServeHTTP(w, r)
http.StripPrefix(
"/example/"+exampleType+"/"+exampleLink+"/",
http.FileServer(http.Dir(fiddle)),
).ServeHTTP(res, req)
return
}
@@ -91,16 +96,17 @@ func serve(addr string) error {
exampleType == "js",
}
err = temp.Execute(w, data)
err = temp.Execute(res, data)
if err != nil {
panic(err)
}
return
}
}
// Serve the main page
err := homeTemplate.Execute(w, examples)
err := homeTemplate.Execute(res, examples)
if err != nil {
panic(err)
}

View File

@@ -15,7 +15,8 @@ import (
var peerConnection *webrtc.PeerConnection //nolint
func doSignaling(w http.ResponseWriter, r *http.Request) {
// nolint: cyclop
func doSignaling(res http.ResponseWriter, req *http.Request) {
var err error
if peerConnection == nil {
@@ -42,7 +43,7 @@ func doSignaling(w http.ResponseWriter, r *http.Request) {
}
var offer webrtc.SessionDescription
if err = json.NewDecoder(r.Body).Decode(&offer); err != nil {
if err = json.NewDecoder(req.Body).Decode(&offer); err != nil {
panic(err)
}
@@ -70,8 +71,8 @@ func doSignaling(w http.ResponseWriter, r *http.Request) {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(response); err != nil {
res.Header().Set("Content-Type", "application/json")
if _, err := res.Write(response); err != nil {
panic(err)
}
}

View File

@@ -20,7 +20,7 @@ import (
var api *webrtc.API //nolint
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
func doSignaling(w http.ResponseWriter, r *http.Request) {
func doSignaling(res http.ResponseWriter, req *http.Request) {
peerConnection, err := api.NewPeerConnection(webrtc.Configuration{})
if err != nil {
panic(err)
@@ -44,7 +44,7 @@ func doSignaling(w http.ResponseWriter, r *http.Request) {
})
var offer webrtc.SessionDescription
if err = json.NewDecoder(r.Body).Decode(&offer); err != nil {
if err = json.NewDecoder(req.Body).Decode(&offer); err != nil {
panic(err)
}
@@ -72,8 +72,8 @@ func doSignaling(w http.ResponseWriter, r *http.Request) {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(response); err != nil {
res.Header().Set("Content-Type", "application/json")
if _, err := res.Write(response); err != nil {
panic(err)
}
}

View File

@@ -21,7 +21,7 @@ import (
var api *webrtc.API //nolint
func doSignaling(w http.ResponseWriter, r *http.Request) {
func doSignaling(res http.ResponseWriter, req *http.Request) { //nolint:cyclop
peerConnection, err := api.NewPeerConnection(webrtc.Configuration{})
if err != nil {
panic(err)
@@ -48,7 +48,7 @@ func doSignaling(w http.ResponseWriter, r *http.Request) {
})
var offer webrtc.SessionDescription
if err = json.NewDecoder(r.Body).Decode(&offer); err != nil {
if err = json.NewDecoder(req.Body).Decode(&offer); err != nil {
panic(err)
}
@@ -76,12 +76,13 @@ func doSignaling(w http.ResponseWriter, r *http.Request) {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(response); err != nil {
res.Header().Set("Content-Type", "application/json")
if _, err := res.Write(response); err != nil {
panic(err)
}
}
//nolint:cyclop
func main() {
settingEngine := webrtc.SettingEngine{}

View File

@@ -26,7 +26,7 @@ import (
const cipherKey = 0xAA
// nolint:gocognit
// nolint:gocognit, cyclop
func main() {
peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
@@ -45,7 +45,9 @@ func main() {
}()
// Create a video track
videoTrack, err := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion")
videoTrack, err := webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion",
)
if err != nil {
panic(err)
}
@@ -88,7 +90,9 @@ func main() {
// It is important to use a time.Ticker instead of time.Sleep because
// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data
// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343)
ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
ticker := time.NewTicker(
time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000),
)
defer ticker.Stop()
for range ticker.C {
frame, _, ivfErr := ivf.ParseNextFrame()
@@ -123,18 +127,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -176,7 +181,7 @@ func main() {
select {}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -193,10 +198,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -206,7 +212,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -30,6 +30,7 @@ const (
videoFileName = "output.ivf"
)
// nolint:cyclop
func main() {
isOffer := flag.Bool("offer", false, "Act as the offerer if set")
port := flag.Int("port", 8080, "http server port")
@@ -45,13 +46,13 @@ func main() {
}
// Use default Codecs
m := &webrtc.MediaEngine{}
if err := m.RegisterDefaultCodecs(); err != nil {
mediaEngine := &webrtc.MediaEngine{}
if err := mediaEngine.RegisterDefaultCodecs(); err != nil {
panic(err)
}
// Create an API object
api := webrtc.NewAPI(webrtc.WithMediaEngine(m))
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
// Create the ICE gatherer
gatherer, err := api.NewICEGatherer(iceOptions)
@@ -74,7 +75,7 @@ func main() {
rtpSendParameters webrtc.RTPSendParameters
)
if *isOffer {
if *isOffer { //nolint:nestif
// Open the video file
file, fileErr := os.Open(videoFileName)
if fileErr != nil {
@@ -109,8 +110,8 @@ func main() {
}
gatherFinished := make(chan struct{})
gatherer.OnLocalCandidate(func(i *webrtc.ICECandidate) {
if i == nil {
gatherer.OnLocalCandidate(func(candidate *webrtc.ICECandidate) {
if candidate == nil {
close(gatherFinished)
}
})
@@ -137,7 +138,7 @@ func main() {
panic(err)
}
s := Signal{
signal := Signal{
ICECandidates: iceCandidates,
ICEParameters: iceParams,
DTLSParameters: dtlsParams,
@@ -147,7 +148,7 @@ func main() {
iceRole := webrtc.ICERoleControlled
// Exchange the information
fmt.Println(encode(&s))
fmt.Println(encode(&signal))
remoteSignal := Signal{}
if *isOffer {
@@ -196,7 +197,7 @@ func main() {
select {}
}
// Given a FourCC value return a Track
// Given a FourCC value return a Track.
func fourCCToTrack(fourCC string) *webrtc.TrackLocalStaticSample {
// Determine video codec
var trackCodec string
@@ -220,9 +221,11 @@ func fourCCToTrack(fourCC string) *webrtc.TrackLocalStaticSample {
return trackLocal
}
// Write a file to Track
// Write a file to Track.
func writeFileToTrack(ivf *ivfreader.IVFReader, header *ivfreader.IVFFileHeader, track *webrtc.TrackLocalStaticSample) {
ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
ticker := time.NewTicker(
time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000),
)
defer ticker.Stop()
for ; true; <-ticker.C {
frame, _, err := ivf.ParseNextFrame()
@@ -251,7 +254,7 @@ type Signal struct {
RTPSendParameters webrtc.RTPSendParameters `json:"rtpSendParameters"`
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -268,10 +271,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *Signal) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -281,7 +285,7 @@ func encode(obj *Signal) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *Signal) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {
@@ -293,12 +297,12 @@ func decode(in string, obj *Signal) {
}
}
// httpSDPServer starts a HTTP Server that consumes SDPs
// httpSDPServer starts a HTTP Server that consumes SDPs.
func httpSDPServer(port int) chan string {
sdpChan := make(chan string)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
fmt.Fprintf(w, "done") //nolint: errcheck
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
body, _ := io.ReadAll(req.Body)
fmt.Fprintf(res, "done") //nolint: errcheck
sdpChan <- string(body)
})

View File

@@ -25,6 +25,7 @@ import (
"github.com/pion/webrtc/v4"
)
// nolint:cyclop
func main() {
isOffer := flag.Bool("offer", false, "Act as the offerer if set")
port := flag.Int("port", 8080, "http server port")
@@ -72,8 +73,8 @@ func main() {
})
gatherFinished := make(chan struct{})
gatherer.OnLocalCandidate(func(i *webrtc.ICECandidate) {
if i == nil {
gatherer.OnLocalCandidate(func(candidate *webrtc.ICECandidate) {
if candidate == nil {
close(gatherFinished)
}
})
@@ -181,7 +182,10 @@ type Signal struct {
func handleOnOpen(channel *webrtc.DataChannel) func() {
return func() {
fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", channel.Label(), channel.ID())
fmt.Printf(
"Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n",
channel.Label(), channel.ID(),
)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
@@ -199,7 +203,7 @@ func handleOnOpen(channel *webrtc.DataChannel) func() {
}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -216,10 +220,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj Signal) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -229,7 +234,7 @@ func encode(obj Signal) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *Signal) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {
@@ -241,12 +246,12 @@ func decode(in string, obj *Signal) {
}
}
// httpSDPServer starts a HTTP Server that consumes SDPs
// httpSDPServer starts a HTTP Server that consumes SDPs.
func httpSDPServer(port int) chan string {
sdpChan := make(chan string)
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
body, _ := io.ReadAll(r.Body)
fmt.Fprintf(w, "done") //nolint: errcheck
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
body, _ := io.ReadAll(req.Body)
fmt.Fprintf(res, "done") //nolint: errcheck
sdpChan <- string(body)
})

View File

@@ -19,8 +19,8 @@ import (
"github.com/pion/webrtc/v4"
)
func signalCandidate(addr string, c *webrtc.ICECandidate) error {
payload := []byte(c.ToJSON().Candidate)
func signalCandidate(addr string, candidate *webrtc.ICECandidate) error {
payload := []byte(candidate.ToJSON().Candidate)
resp, err := http.Post(fmt.Sprintf("http://%s/candidate", addr), // nolint:noctx
"application/json; charset=utf-8", bytes.NewReader(payload))
if err != nil {
@@ -30,7 +30,8 @@ func signalCandidate(addr string, c *webrtc.ICECandidate) error {
return resp.Body.Close()
}
func main() { // nolint:gocognit
// nolint:gocognit, cyclop
func main() {
offerAddr := flag.String("offer-address", "localhost:50000", "Address that the Offer HTTP server is hosted on.")
answerAddr := flag.String("answer-address", ":60000", "Address that the Answer HTTP server is hosted on.")
flag.Parse()
@@ -61,8 +62,8 @@ func main() { // nolint:gocognit
// When an ICE candidate is available send to the other Pion instance
// the other Pion instance will add this candidate by calling AddICECandidate
peerConnection.OnICECandidate(func(c *webrtc.ICECandidate) {
if c == nil {
peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate == nil {
return
}
@@ -71,8 +72,8 @@ func main() { // nolint:gocognit
desc := peerConnection.RemoteDescription()
if desc == nil {
pendingCandidates = append(pendingCandidates, c)
} else if onICECandidateErr := signalCandidate(*offerAddr, c); onICECandidateErr != nil {
pendingCandidates = append(pendingCandidates, candidate)
} else if onICECandidateErr := signalCandidate(*offerAddr, candidate); onICECandidateErr != nil {
panic(onICECandidateErr)
}
})
@@ -80,20 +81,22 @@ func main() { // nolint:gocognit
// A HTTP handler that allows the other Pion instance to send us ICE candidates
// This allows us to add ICE candidates faster, we don't have to wait for STUN or TURN
// candidates which may be slower
http.HandleFunc("/candidate", func(w http.ResponseWriter, r *http.Request) { //nolint: revive
candidate, candidateErr := io.ReadAll(r.Body)
http.HandleFunc("/candidate", func(res http.ResponseWriter, req *http.Request) { //nolint: revive
candidate, candidateErr := io.ReadAll(req.Body)
if candidateErr != nil {
panic(candidateErr)
}
if candidateErr := peerConnection.AddICECandidate(webrtc.ICECandidateInit{Candidate: string(candidate)}); candidateErr != nil {
if candidateErr := peerConnection.AddICECandidate(
webrtc.ICECandidateInit{Candidate: string(candidate)},
); candidateErr != nil {
panic(candidateErr)
}
})
// A HTTP handler that processes a SessionDescription given to us from the other Pion process
http.HandleFunc("/sdp", func(w http.ResponseWriter, r *http.Request) { // nolint: revive
http.HandleFunc("/sdp", func(res http.ResponseWriter, req *http.Request) { // nolint: revive
sdp := webrtc.SessionDescription{}
if err := json.NewDecoder(r.Body).Decode(&sdp); err != nil {
if err := json.NewDecoder(req.Body).Decode(&sdp); err != nil {
panic(err)
}
@@ -112,7 +115,11 @@ func main() { // nolint:gocognit
if err != nil {
panic(err)
}
resp, err := http.Post(fmt.Sprintf("http://%s/sdp", *offerAddr), "application/json; charset=utf-8", bytes.NewReader(payload)) // nolint:noctx
resp, err := http.Post( //nolint:noctx
fmt.Sprintf("http://%s/sdp", *offerAddr),
"application/json; charset=utf-8",
bytes.NewReader(payload),
) // nolint:noctx
if err != nil {
panic(err)
} else if closeErr := resp.Body.Close(); closeErr != nil {
@@ -137,18 +144,19 @@ func main() { // nolint:gocognit
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -156,32 +164,37 @@ func main() { // nolint:gocognit
})
// Register data channel creation handling
peerConnection.OnDataChannel(func(d *webrtc.DataChannel) {
fmt.Printf("New DataChannel %s %d\n", d.Label(), d.ID())
peerConnection.OnDataChannel(func(dataChannel *webrtc.DataChannel) {
fmt.Printf("New DataChannel %s %d\n", dataChannel.Label(), dataChannel.ID())
// Register channel opening handling
d.OnOpen(func() {
fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", d.Label(), d.ID())
dataChannel.OnOpen(func() {
fmt.Printf(
"Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n",
dataChannel.Label(), dataChannel.ID(),
)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
message, sendTextErr := randutil.GenerateCryptoRandomString(15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
message, sendTextErr := randutil.GenerateCryptoRandomString(
15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
)
if sendTextErr != nil {
panic(sendTextErr)
}
// Send the message as text
fmt.Printf("Sending '%s'\n", message)
if sendTextErr = d.SendText(message); sendTextErr != nil {
if sendTextErr = dataChannel.SendText(message); sendTextErr != nil {
panic(sendTextErr)
}
}
})
// Register text message handling
d.OnMessage(func(msg webrtc.DataChannelMessage) {
fmt.Printf("Message from DataChannel '%s': '%s'\n", d.Label(), string(msg.Data))
dataChannel.OnMessage(func(msg webrtc.DataChannelMessage) {
fmt.Printf("Message from DataChannel '%s': '%s'\n", dataChannel.Label(), string(msg.Data))
})
})

View File

@@ -19,9 +19,13 @@ import (
"github.com/pion/webrtc/v4"
)
func signalCandidate(addr string, c *webrtc.ICECandidate) error {
payload := []byte(c.ToJSON().Candidate)
resp, err := http.Post(fmt.Sprintf("http://%s/candidate", addr), "application/json; charset=utf-8", bytes.NewReader(payload)) //nolint:noctx
func signalCandidate(addr string, candidate *webrtc.ICECandidate) error {
payload := []byte(candidate.ToJSON().Candidate)
resp, err := http.Post( //nolint:noctx
fmt.Sprintf("http://%s/candidate", addr),
"application/json; charset=utf-8",
bytes.NewReader(payload),
)
if err != nil {
return err
}
@@ -29,7 +33,8 @@ func signalCandidate(addr string, c *webrtc.ICECandidate) error {
return resp.Body.Close()
}
func main() { //nolint:gocognit
//nolint:gocognit, cyclop
func main() {
offerAddr := flag.String("offer-address", ":50000", "Address that the Offer HTTP server is hosted on.")
answerAddr := flag.String("answer-address", "127.0.0.1:60000", "Address that the Answer HTTP server is hosted on.")
flag.Parse()
@@ -61,8 +66,8 @@ func main() { //nolint:gocognit
// When an ICE candidate is available send to the other Pion instance
// the other Pion instance will add this candidate by calling AddICECandidate
peerConnection.OnICECandidate(func(c *webrtc.ICECandidate) {
if c == nil {
peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate == nil {
return
}
@@ -71,8 +76,8 @@ func main() { //nolint:gocognit
desc := peerConnection.RemoteDescription()
if desc == nil {
pendingCandidates = append(pendingCandidates, c)
} else if onICECandidateErr := signalCandidate(*answerAddr, c); onICECandidateErr != nil {
pendingCandidates = append(pendingCandidates, candidate)
} else if onICECandidateErr := signalCandidate(*answerAddr, candidate); onICECandidateErr != nil {
panic(onICECandidateErr)
}
})
@@ -80,20 +85,22 @@ func main() { //nolint:gocognit
// A HTTP handler that allows the other Pion instance to send us ICE candidates
// This allows us to add ICE candidates faster, we don't have to wait for STUN or TURN
// candidates which may be slower
http.HandleFunc("/candidate", func(w http.ResponseWriter, r *http.Request) { //nolint: revive
candidate, candidateErr := io.ReadAll(r.Body)
http.HandleFunc("/candidate", func(res http.ResponseWriter, req *http.Request) { //nolint: revive
candidate, candidateErr := io.ReadAll(req.Body)
if candidateErr != nil {
panic(candidateErr)
}
if candidateErr := peerConnection.AddICECandidate(webrtc.ICECandidateInit{Candidate: string(candidate)}); candidateErr != nil {
if candidateErr := peerConnection.AddICECandidate(
webrtc.ICECandidateInit{Candidate: string(candidate)},
); candidateErr != nil {
panic(candidateErr)
}
})
// A HTTP handler that processes a SessionDescription given to us from the other Pion process
http.HandleFunc("/sdp", func(w http.ResponseWriter, r *http.Request) { //nolint: revive
http.HandleFunc("/sdp", func(res http.ResponseWriter, req *http.Request) { //nolint: revive
sdp := webrtc.SessionDescription{}
if sdpErr := json.NewDecoder(r.Body).Decode(&sdp); sdpErr != nil {
if sdpErr := json.NewDecoder(req.Body).Decode(&sdp); sdpErr != nil {
panic(sdpErr)
}
@@ -122,18 +129,19 @@ func main() { //nolint:gocognit
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -142,12 +150,17 @@ func main() { //nolint:gocognit
// Register channel opening handling
dataChannel.OnOpen(func() {
fmt.Printf("Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n", dataChannel.Label(), dataChannel.ID())
fmt.Printf(
"Data channel '%s'-'%d' open. Random messages will now be sent to any connected DataChannels every 5 seconds\n",
dataChannel.Label(), dataChannel.ID(),
)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
for range ticker.C {
message, sendTextErr := randutil.GenerateCryptoRandomString(15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
message, sendTextErr := randutil.GenerateCryptoRandomString(
15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
)
if sendTextErr != nil {
panic(sendTextErr)
}
@@ -182,7 +195,11 @@ func main() { //nolint:gocognit
if err != nil {
panic(err)
}
resp, err := http.Post(fmt.Sprintf("http://%s/sdp", *answerAddr), "application/json; charset=utf-8", bytes.NewReader(payload)) // nolint:noctx
resp, err := http.Post( //nolint:noctx
fmt.Sprintf("http://%s/sdp", *answerAddr),
"application/json; charset=utf-8",
bytes.NewReader(payload),
)
if err != nil {
panic(err)
} else if err := resp.Body.Close(); err != nil {

View File

@@ -24,10 +24,10 @@ import (
var peerConnection *webrtc.PeerConnection //nolint
// doSignaling exchanges all state of the local PeerConnection and is called
// every time a video is added or removed
func doSignaling(w http.ResponseWriter, r *http.Request) {
// every time a video is added or removed.
func doSignaling(res http.ResponseWriter, req *http.Request) {
var offer webrtc.SessionDescription
if err := json.NewDecoder(r.Body).Decode(&offer); err != nil {
if err := json.NewDecoder(req.Body).Decode(&offer); err != nil {
panic(err)
}
@@ -55,24 +55,24 @@ func doSignaling(w http.ResponseWriter, r *http.Request) {
panic(err)
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(response); err != nil {
res.Header().Set("Content-Type", "application/json")
if _, err := res.Write(response); err != nil {
panic(err)
}
}
// Add a single video track
func createPeerConnection(w http.ResponseWriter, r *http.Request) {
// Add a single video track.
func createPeerConnection(res http.ResponseWriter, req *http.Request) {
if peerConnection.ConnectionState() != webrtc.PeerConnectionStateNew {
panic(fmt.Sprintf("createPeerConnection called in non-new state (%s)", peerConnection.ConnectionState()))
}
doSignaling(w, r)
doSignaling(res, req)
fmt.Println("PeerConnection has been created")
}
// Add a single video track
func addVideo(w http.ResponseWriter, r *http.Request) {
// Add a single video track.
func addVideo(res http.ResponseWriter, req *http.Request) {
videoTrack, err := webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8},
fmt.Sprintf("video-%d", randutil.NewMathRandomGenerator().Uint32()),
@@ -99,19 +99,19 @@ func addVideo(w http.ResponseWriter, r *http.Request) {
}()
go writeVideoToTrack(videoTrack)
doSignaling(w, r)
doSignaling(res, req)
fmt.Println("Video track has been added")
}
// Remove a single sender
func removeVideo(w http.ResponseWriter, r *http.Request) {
// Remove a single sender.
func removeVideo(res http.ResponseWriter, req *http.Request) {
if senders := peerConnection.GetSenders(); len(senders) != 0 {
if err := peerConnection.RemoveTrack(senders[0]); err != nil {
panic(err)
}
}
doSignaling(w, r)
doSignaling(res, req)
fmt.Println("Video track has been removed")
}
@@ -130,18 +130,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -164,8 +165,8 @@ func main() {
}
// Read a video file from disk and write it to a webrtc.Track
// When the video has been completely read this exits without error
func writeVideoToTrack(t *webrtc.TrackLocalStaticSample) {
// When the video has been completely read this exits without error.
func writeVideoToTrack(track *webrtc.TrackLocalStaticSample) {
// Open a IVF file and start reading using our IVFReader
file, err := os.Open("output.ivf")
if err != nil {
@@ -183,17 +184,21 @@ func writeVideoToTrack(t *webrtc.TrackLocalStaticSample) {
// It is important to use a time.Ticker instead of time.Sleep because
// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data
// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343)
ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
ticker := time.NewTicker(
time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000),
)
defer ticker.Stop()
for ; true; <-ticker.C {
frame, _, err := ivf.ParseNextFrame()
if err != nil {
fmt.Printf("Finish writing video track: %s ", err)
return
}
if err = t.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil {
if err = track.WriteSample(media.Sample{Data: frame, Duration: time.Second}); err != nil {
fmt.Printf("Finish writing video track: %s ", err)
return
}
}

View File

@@ -31,8 +31,7 @@ const (
oggPageDuration = time.Millisecond * 20
)
// nolint:gocognit
func main() {
func main() { //nolint:gocognit,cyclop,gocyclo,maintidx
// Assert that we have an audio or video file
_, err := os.Stat(videoFileName)
haveVideoFile := !os.IsNotExist(err)
@@ -63,7 +62,7 @@ func main() {
iceConnectedCtx, iceConnectedCtxCancel := context.WithCancel(context.Background())
if haveVideoFile {
if haveVideoFile { //nolint:nestif
file, openErr := os.Open(videoFileName)
if openErr != nil {
panic(openErr)
@@ -88,7 +87,9 @@ func main() {
}
// Create a video track
videoTrack, videoTrackErr := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: trackCodec}, "video", "pion")
videoTrack, videoTrackErr := webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: trackCodec}, "video", "pion",
)
if videoTrackErr != nil {
panic(videoTrackErr)
}
@@ -131,7 +132,9 @@ func main() {
// It is important to use a time.Ticker instead of time.Sleep because
// * avoids accumulating skew, just calling time.Sleep didn't compensate for the time spent parsing the data
// * works around latency issues with Sleep (see https://github.com/golang/go/issues/44343)
ticker := time.NewTicker(time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000))
ticker := time.NewTicker(
time.Millisecond * time.Duration((float32(header.TimebaseNumerator)/float32(header.TimebaseDenominator))*1000),
)
defer ticker.Stop()
for ; true; <-ticker.C {
frame, _, ivfErr := ivf.ParseNextFrame()
@@ -151,9 +154,11 @@ func main() {
}()
}
if haveAudioFile {
if haveAudioFile { //nolint:nestif
// Create a audio track
audioTrack, audioTrackErr := webrtc.NewTrackLocalStaticSample(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, "audio", "pion")
audioTrack, audioTrackErr := webrtc.NewTrackLocalStaticSample(
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus}, "audio", "pion",
)
if audioTrackErr != nil {
panic(audioTrackErr)
}
@@ -233,18 +238,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -286,7 +292,7 @@ func main() {
select {}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -303,10 +309,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -316,7 +323,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -22,18 +22,20 @@ import (
"github.com/pion/webrtc/v4"
)
// nolint:gocognit
// nolint:gocognit, cyclop
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
// Create a MediaEngine object to configure the supported codec
m := &webrtc.MediaEngine{}
mediaEngine := &webrtc.MediaEngine{}
// Setup the codecs you want to use.
// We'll use a VP8 and Opus but you can also define your own
if err := m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 96,
if err := mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
},
PayloadType: 96,
}, webrtc.RTPCodecTypeVideo); err != nil {
panic(err)
}
@@ -42,10 +44,10 @@ func main() {
// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
// for each PeerConnection.
i := &interceptor.Registry{}
interceptorRegistry := &interceptor.Registry{}
// Use the default set of Interceptors
if err := webrtc.RegisterDefaultInterceptors(m, i); err != nil {
if err := webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil {
panic(err)
}
@@ -57,10 +59,10 @@ func main() {
if err != nil {
panic(err)
}
i.Add(intervalPliFactory)
interceptorRegistry.Add(intervalPliFactory)
// Create the API object with the MediaEngine
api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i))
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine), webrtc.WithInterceptorRegistry(interceptorRegistry))
// Prepare the configuration
config := webrtc.Configuration{
@@ -82,7 +84,9 @@ func main() {
}()
// Create Track that we send video back to browser on
outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion")
outputTrack, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion",
)
if err != nil {
panic(err)
}
@@ -134,18 +138,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -178,7 +183,7 @@ func main() {
select {}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -195,10 +200,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -208,7 +214,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -101,7 +101,7 @@ func main() {
select {}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -118,10 +118,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -131,7 +132,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -30,22 +30,25 @@ type udpConn struct {
payloadType uint8
}
// nolint:gocognit
func main() {
func main() { //nolint:gocognit,cyclop,maintidx
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
// Create a MediaEngine object to configure the supported codec
m := &webrtc.MediaEngine{}
mediaEngine := &webrtc.MediaEngine{}
// Setup the codecs you want to use.
// We'll use a VP8 and Opus but you can also define your own
if err := m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
if err := mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
},
}, webrtc.RTPCodecTypeVideo); err != nil {
panic(err)
}
if err := m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
if err := mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
},
}, webrtc.RTPCodecTypeAudio); err != nil {
panic(err)
}
@@ -54,7 +57,7 @@ func main() {
// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
// for each PeerConnection.
i := &interceptor.Registry{}
interceptorRegistry := &interceptor.Registry{}
// Register a intervalpli factory
// This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender.
@@ -64,15 +67,15 @@ func main() {
if err != nil {
panic(err)
}
i.Add(intervalPliFactory)
interceptorRegistry.Add(intervalPliFactory)
// Use the default set of Interceptors
if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil {
if err = webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil {
panic(err)
}
// Create the API object with the MediaEngine
api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i))
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine), webrtc.WithInterceptorRegistry(interceptorRegistry))
// Prepare the configuration
config := webrtc.Configuration{
@@ -114,22 +117,22 @@ func main() {
"audio": {port: 4000, payloadType: 111},
"video": {port: 4002, payloadType: 96},
}
for _, c := range udpConns {
for _, conn := range udpConns {
// Create remote addr
var raddr *net.UDPAddr
if raddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", c.port)); err != nil {
if raddr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("127.0.0.1:%d", conn.port)); err != nil {
panic(err)
}
// Dial udp
if c.conn, err = net.DialUDP("udp", laddr, raddr); err != nil {
if conn.conn, err = net.DialUDP("udp", laddr, raddr); err != nil {
panic(err)
}
defer func(conn net.PacketConn) {
if closeErr := conn.Close(); closeErr != nil {
panic(closeErr)
}
}(c.conn)
}(conn.conn)
}
// Set a handler for when a new remote track starts, this handler will forward data to
@@ -137,33 +140,33 @@ func main() {
// In your application this is where you would handle/process audio/video
peerConnection.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) { //nolint: revive
// Retrieve udp connection
c, ok := udpConns[track.Kind().String()]
conn, ok := udpConns[track.Kind().String()]
if !ok {
return
}
b := make([]byte, 1500)
buf := make([]byte, 1500)
rtpPacket := &rtp.Packet{}
for {
// Read
n, _, readErr := track.Read(b)
n, _, readErr := track.Read(buf)
if readErr != nil {
panic(readErr)
}
// Unmarshal the packet and update the PayloadType
if err = rtpPacket.Unmarshal(b[:n]); err != nil {
if err = rtpPacket.Unmarshal(buf[:n]); err != nil {
panic(err)
}
rtpPacket.PayloadType = c.payloadType
rtpPacket.PayloadType = conn.payloadType
// Marshal into original buffer with updated PayloadType
if n, err = rtpPacket.MarshalTo(b); err != nil {
if n, err = rtpPacket.MarshalTo(buf); err != nil {
panic(err)
}
// Write
if _, writeErr := c.conn.Write(b[:n]); writeErr != nil {
if _, writeErr := conn.conn.Write(buf[:n]); writeErr != nil {
// For this particular example, third party applications usually timeout after a short
// amount of time during which the user doesn't have enough time to provide the answer
// to the browser.
@@ -191,18 +194,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Done forwarding")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Done forwarding")
os.Exit(0)
@@ -244,7 +248,7 @@ func main() {
select {}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -261,10 +265,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -274,7 +279,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -21,6 +21,7 @@ import (
"github.com/pion/webrtc/v4"
)
// nolint:cyclop
func main() {
peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
@@ -54,7 +55,9 @@ func main() {
}()
// Create a video track
videoTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion")
videoTrack, err := webrtc.NewTrackLocalStaticRTP(
webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion",
)
if err != nil {
panic(err)
}
@@ -137,7 +140,7 @@ func main() {
}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -154,10 +157,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -167,7 +171,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -24,9 +24,9 @@ import (
"github.com/pion/webrtc/v4/pkg/media/ivfwriter"
)
func saveToDisk(i media.Writer, track *webrtc.TrackRemote) {
func saveToDisk(writer media.Writer, track *webrtc.TrackRemote) {
defer func() {
if err := i.Close(); err != nil {
if err := writer.Close(); err != nil {
panic(err)
}
}()
@@ -35,26 +35,35 @@ func saveToDisk(i media.Writer, track *webrtc.TrackRemote) {
rtpPacket, _, err := track.ReadRTP()
if err != nil {
fmt.Println(err)
return
}
if err := i.WriteRTP(rtpPacket); err != nil {
if err := writer.WriteRTP(rtpPacket); err != nil {
fmt.Println(err)
return
}
}
}
// nolint:cyclop
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
// Create a MediaEngine object to configure the supported codec
m := &webrtc.MediaEngine{}
mediaEngine := &webrtc.MediaEngine{}
// Setup the codecs you want to use.
// We'll use a VP8 and Opus but you can also define your own
if err := m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeAV1, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 96,
if err := mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeAV1,
ClockRate: 90000,
Channels: 0,
SDPFmtpLine: "",
RTCPFeedback: nil,
},
PayloadType: 96,
}, webrtc.RTPCodecTypeVideo); err != nil {
panic(err)
}
@@ -63,7 +72,7 @@ func main() {
// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
// for each PeerConnection.
i := &interceptor.Registry{}
interceptorRegistry := &interceptor.Registry{}
// Register a intervalpli factory
// This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender.
@@ -73,15 +82,15 @@ func main() {
if err != nil {
panic(err)
}
i.Add(intervalPliFactory)
interceptorRegistry.Add(intervalPliFactory)
// Use the default set of Interceptors
if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil {
if err = webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil {
panic(err)
}
// Create the API object with the MediaEngine
api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i))
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine), webrtc.WithInterceptorRegistry(interceptorRegistry))
// Prepare the configuration
config := webrtc.Configuration{}
@@ -172,7 +181,7 @@ func main() {
select {}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -189,10 +198,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -202,7 +212,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -4,7 +4,8 @@
//go:build !js
// +build !js
// save-to-disk is a simple application that shows how to record your webcam/microphone using Pion WebRTC and save VP8/Opus to disk.
// save-to-disk is a simple application that shows how to record your webcam/microphone using
// Pion WebRTC and save VP8/Opus to disk.
package main
import (
@@ -25,9 +26,9 @@ import (
"github.com/pion/webrtc/v4/pkg/media/oggwriter"
)
func saveToDisk(i media.Writer, track *webrtc.TrackRemote) {
func saveToDisk(writer media.Writer, track *webrtc.TrackRemote) {
defer func() {
if err := i.Close(); err != nil {
if err := writer.Close(); err != nil {
panic(err)
}
}()
@@ -36,33 +37,39 @@ func saveToDisk(i media.Writer, track *webrtc.TrackRemote) {
rtpPacket, _, err := track.ReadRTP()
if err != nil {
fmt.Println(err)
return
}
if err := i.WriteRTP(rtpPacket); err != nil {
if err := writer.WriteRTP(rtpPacket); err != nil {
fmt.Println(err)
return
}
}
}
// nolint:gocognit
// nolint:gocognit, cyclop
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
// Create a MediaEngine object to configure the supported codec
m := &webrtc.MediaEngine{}
mediaEngine := &webrtc.MediaEngine{}
// Setup the codecs you want to use.
// We'll use a VP8 and Opus but you can also define your own
if err := m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 96,
if err := mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
},
PayloadType: 96,
}, webrtc.RTPCodecTypeVideo); err != nil {
panic(err)
}
if err := m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 111,
if err := mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
},
PayloadType: 111,
}, webrtc.RTPCodecTypeAudio); err != nil {
panic(err)
}
@@ -71,7 +78,7 @@ func main() {
// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
// for each PeerConnection.
i := &interceptor.Registry{}
interceptorRegistry := &interceptor.Registry{}
// Register a intervalpli factory
// This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender.
@@ -81,15 +88,15 @@ func main() {
if err != nil {
panic(err)
}
i.Add(intervalPliFactory)
interceptorRegistry.Add(intervalPliFactory)
// Use the default set of Interceptors
if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil {
if err = webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil {
panic(err)
}
// Create the API object with the MediaEngine
api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i))
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine), webrtc.WithInterceptorRegistry(interceptorRegistry))
// Prepare the configuration
config := webrtc.Configuration{
@@ -200,7 +207,7 @@ func main() {
select {}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -217,10 +224,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -230,7 +238,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -22,7 +22,7 @@ import (
"github.com/pion/webrtc/v4"
)
// nolint:gocognit
// nolint:gocognit, cyclop
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
@@ -49,36 +49,52 @@ func main() {
outputTracks := map[string]*webrtc.TrackLocalStaticRTP{}
// Create Track that we send video back to browser on
outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video_q", "pion_q")
outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
}, "video_q", "pion_q")
if err != nil {
panic(err)
}
outputTracks["q"] = outputTrack
outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video_h", "pion_h")
outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
}, "video_h", "pion_h")
if err != nil {
panic(err)
}
outputTracks["h"] = outputTrack
outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video_f", "pion_f")
outputTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
}, "video_f", "pion_f")
if err != nil {
panic(err)
}
outputTracks["f"] = outputTrack
if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionRecvonly}); err != nil {
if _, err = peerConnection.AddTransceiverFromKind(
webrtc.RTPCodecTypeVideo,
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionRecvonly},
); err != nil {
panic(err)
}
// Add this newly created track to the PeerConnection to send back video
if _, err = peerConnection.AddTransceiverFromTrack(outputTracks["q"], webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}); err != nil {
if _, err = peerConnection.AddTransceiverFromTrack(
outputTracks["q"], webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}); err != nil {
panic(err)
}
if _, err = peerConnection.AddTransceiverFromTrack(outputTracks["h"], webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}); err != nil {
if _, err = peerConnection.AddTransceiverFromTrack(
outputTracks["h"],
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly},
); err != nil {
panic(err)
}
if _, err = peerConnection.AddTransceiverFromTrack(outputTracks["f"], webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}); err != nil {
if _, err = peerConnection.AddTransceiverFromTrack(
outputTracks["f"],
webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly},
); err != nil {
panic(err)
}
@@ -117,7 +133,9 @@ func main() {
defer ticker.Stop()
for range ticker.C {
fmt.Printf("Sending pli for stream with rid: %q, ssrc: %d\n", track.RID(), track.SSRC())
if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); writeErr != nil {
if writeErr := peerConnection.WriteRTCP(
[]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}},
); writeErr != nil {
fmt.Println(writeErr)
}
}
@@ -138,18 +156,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -183,7 +202,7 @@ func main() {
select {}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -200,10 +219,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -213,7 +233,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -24,17 +24,17 @@ import (
"github.com/pion/webrtc/v4"
)
// How ofter to print WebRTC stats
// How ofter to print WebRTC stats.
const statsInterval = time.Second * 5
// nolint:gocognit
// nolint:gocognit,cyclop
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
// Create a MediaEngine object to configure the supported codec
m := &webrtc.MediaEngine{}
mediaEngine := &webrtc.MediaEngine{}
if err := m.RegisterDefaultCodecs(); err != nil {
if err := mediaEngine.RegisterDefaultCodecs(); err != nil {
panic(err)
}
@@ -42,7 +42,7 @@ func main() {
// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
// for each PeerConnection.
i := &interceptor.Registry{}
interceptorRegistry := &interceptor.Registry{}
statsInterceptorFactory, err := stats.NewInterceptor()
if err != nil {
@@ -53,15 +53,15 @@ func main() {
statsInterceptorFactory.OnNewPeerConnection(func(_ string, g stats.Getter) {
statsGetter = g
})
i.Add(statsInterceptorFactory)
interceptorRegistry.Add(statsInterceptorFactory)
// Use the default set of Interceptors
if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil {
if err = webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil {
panic(err)
}
// Create the API object with the MediaEngine
api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i))
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine), webrtc.WithInterceptorRegistry(interceptorRegistry))
// Prepare the configuration
config := webrtc.Configuration{
@@ -175,7 +175,7 @@ func main() {
}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -192,10 +192,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -205,7 +206,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -24,6 +24,7 @@ import (
"github.com/pion/webrtc/v4"
)
// nolint: cyclop
func main() { // nolint:gocognit
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
@@ -47,7 +48,9 @@ func main() { // nolint:gocognit
}()
// Create Track that we send video back to browser on
outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeVP8}, "video", "pion")
outputTrack, err := webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeVP8,
}, "video", "pion")
if err != nil {
panic(err)
}
@@ -115,12 +118,14 @@ func main() { // nolint:gocognit
lastTimestamp = oldTimestamp
// Check if this is the current track
if currTrack == trackNum {
if currTrack == trackNum { //nolint:nestif
// If just switched to this track, send PLI to get picture refresh
if !isCurrTrack {
isCurrTrack = true
if track.Kind() == webrtc.RTPCodecTypeVideo {
if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())}}); writeErr != nil {
if writeErr := peerConnection.WriteRTCP([]rtcp.Packet{
&rtcp.PictureLossIndication{MediaSSRC: uint32(track.SSRC())},
}); writeErr != nil {
fmt.Println(writeErr)
}
}
@@ -136,17 +141,18 @@ func main() { // nolint:gocognit
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", s.String())
peerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
done()
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
done()
}
@@ -222,7 +228,7 @@ func main() { // nolint:gocognit
}
}
// Read from stdin until we get a newline
// Read from stdin until we get a newline.
func readUntilNewline() (in string) {
var err error
@@ -239,10 +245,11 @@ func readUntilNewline() (in string) {
}
fmt.Println("")
return
}
// JSON encode + base64 a SessionDescription
// JSON encode + base64 a SessionDescription.
func encode(obj *webrtc.SessionDescription) string {
b, err := json.Marshal(obj)
if err != nil {
@@ -252,7 +259,7 @@ func encode(obj *webrtc.SessionDescription) string {
return base64.StdEncoding.EncodeToString(b)
}
// Decode a base64 and unmarshal JSON into a SessionDescription
// Decode a base64 and unmarshal JSON into a SessionDescription.
func decode(in string, obj *webrtc.SessionDescription) {
b, err := base64.StdEncoding.DecodeString(in)
if err != nil {

View File

@@ -15,7 +15,8 @@ import (
)
// websocketServer is called for every new inbound WebSocket
func websocketServer(ws *websocket.Conn) { // nolint:gocognit
// nolint: gocognit, cyclop
func websocketServer(wsConn *websocket.Conn) {
// Create a new RTCPeerConnection
peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{})
if err != nil {
@@ -26,17 +27,17 @@ func websocketServer(ws *websocket.Conn) { // nolint:gocognit
// ice trickle is implemented. Everytime we have a new candidate available we send
// it as soon as it is ready. We don't wait to emit a Offer/Answer until they are
// all available
peerConnection.OnICECandidate(func(c *webrtc.ICECandidate) {
if c == nil {
peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate == nil {
return
}
outbound, marshalErr := json.Marshal(c.ToJSON())
outbound, marshalErr := json.Marshal(candidate.ToJSON())
if marshalErr != nil {
panic(marshalErr)
}
if _, err = ws.Write(outbound); err != nil {
if _, err = wsConn.Write(outbound); err != nil {
panic(err)
}
})
@@ -61,7 +62,7 @@ func websocketServer(ws *websocket.Conn) { // nolint:gocognit
buf := make([]byte, 1500)
for {
// Read each inbound WebSocket Message
n, err := ws.Read(buf)
n, err := wsConn.Read(buf)
if err != nil {
panic(err)
}
@@ -94,7 +95,7 @@ func websocketServer(ws *websocket.Conn) { // nolint:gocognit
panic(marshalErr)
}
if _, err = ws.Write(outbound); err != nil {
if _, err = wsConn.Write(outbound); err != nil {
panic(err)
}
// Attempt to unmarshal as a ICECandidateInit. If the candidate field is empty

View File

@@ -38,6 +38,7 @@ import (
+--------------------+ +--------------------+
*/
// nolint:cyclop
func main() {
var inboundBytes int32 // for offerPeerConnection
var outboundBytes int32 // for offerPeerConnection
@@ -50,24 +51,25 @@ func main() {
panicIfError(err)
// Add a filter that monitors the traffic on the router
wan.AddChunkFilter(func(c vnet.Chunk) bool {
netType := c.SourceAddr().Network()
wan.AddChunkFilter(func(chunk vnet.Chunk) bool {
netType := chunk.SourceAddr().Network()
if netType == "udp" {
dstAddr := c.DestinationAddr().String()
dstAddr := chunk.DestinationAddr().String()
host, _, err2 := net.SplitHostPort(dstAddr)
panicIfError(err2)
if host == "1.2.3.4" {
// c.UserData() returns a []byte of UDP payload
atomic.AddInt32(&inboundBytes, int32(len(c.UserData())))
atomic.AddInt32(&inboundBytes, int32(len(chunk.UserData()))) //nolint:gosec // G115
}
srcAddr := c.SourceAddr().String()
srcAddr := chunk.SourceAddr().String()
host, _, err2 = net.SplitHostPort(srcAddr)
panicIfError(err2)
if host == "1.2.3.4" {
// c.UserData() returns a []byte of UDP payload
atomic.AddInt32(&outboundBytes, int32(len(c.UserData())))
atomic.AddInt32(&outboundBytes, int32(len(chunk.UserData()))) //nolint:gosec // G115
}
}
return true
})
@@ -133,18 +135,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
offerPeerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (offerer)\n", s.String())
offerPeerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (offerer)\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -153,18 +156,19 @@ func main() {
// Set the handler for Peer connection state
// This will notify you when the peer has connected/disconnected
answerPeerConnection.OnConnectionStateChange(func(s webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (answerer)\n", s.String())
answerPeerConnection.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
fmt.Printf("Peer Connection State has changed: %s (answerer)\n", state.String())
if s == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure. It may be reconnected using an ICE Restart.
if state == webrtc.PeerConnectionStateFailed {
// Wait until PeerConnection has had no network activity for 30 seconds or another failure.
// It may be reconnected using an ICE Restart.
// Use webrtc.PeerConnectionStateDisconnected if you are interested in detecting faster timeout.
// Note that the PeerConnection may come back from PeerConnectionStateDisconnected.
fmt.Println("Peer Connection has gone to failed exiting")
os.Exit(0)
}
if s == webrtc.PeerConnectionStateClosed {
if state == webrtc.PeerConnectionStateClosed {
// PeerConnection was explicitly closed. This usually happens from a DTLS CloseNotify
fmt.Println("Peer Connection has gone to closed exiting")
os.Exit(0)
@@ -173,17 +177,17 @@ func main() {
// Set ICE Candidate handler. As soon as a PeerConnection has gathered a candidate
// send it to the other peer
answerPeerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
if i != nil {
panicIfError(offerPeerConnection.AddICECandidate(i.ToJSON()))
answerPeerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate != nil {
panicIfError(offerPeerConnection.AddICECandidate(candidate.ToJSON()))
}
})
// Set ICE Candidate handler. As soon as a PeerConnection has gathered a candidate
// send it to the other peer
offerPeerConnection.OnICECandidate(func(i *webrtc.ICECandidate) {
if i != nil {
panicIfError(answerPeerConnection.AddICECandidate(i.ToJSON()))
offerPeerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate != nil {
panicIfError(answerPeerConnection.AddICECandidate(candidate.ToJSON()))
}
})

View File

@@ -4,7 +4,8 @@
//go:build !js
// +build !js
// whip-whep demonstrates how to use the WHIP/WHEP specifications to exchange SPD descriptions and stream media to a WebRTC client in the browser or OBS
// whip-whep demonstrates how to use the WHIP/WHEP specifications to exchange SPD descriptions
// and stream media to a WebRTC client in the browser or OBS.
package main
import (
@@ -34,7 +35,9 @@ var (
func main() {
// Everything below is the Pion WebRTC API! Thanks for using it ❤️.
var err error
if videoTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264}, "video", "pion"); err != nil {
if videoTrack, err = webrtc.NewTrackLocalStaticRTP(webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH264,
}, "video", "pion"); err != nil {
panic(err)
}
@@ -46,21 +49,23 @@ func main() {
panic(http.ListenAndServe(":8080", nil)) // nolint: gosec
}
func whipHandler(w http.ResponseWriter, r *http.Request) {
func whipHandler(res http.ResponseWriter, req *http.Request) {
// Read the offer from HTTP Request
offer, err := io.ReadAll(r.Body)
offer, err := io.ReadAll(req.Body)
if err != nil {
panic(err)
}
// Create a MediaEngine object to configure the supported codec
m := &webrtc.MediaEngine{}
mediaEngine := &webrtc.MediaEngine{}
// Setup the codecs you want to use.
// We'll only use H264 but you can also define your own
if err = m.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{MimeType: webrtc.MimeTypeH264, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 96,
if err = mediaEngine.RegisterCodec(webrtc.RTPCodecParameters{
RTPCodecCapability: webrtc.RTPCodecCapability{
MimeType: webrtc.MimeTypeH264, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
},
PayloadType: 96,
}, webrtc.RTPCodecTypeVideo); err != nil {
panic(err)
}
@@ -69,7 +74,7 @@ func whipHandler(w http.ResponseWriter, r *http.Request) {
// This provides NACKs, RTCP Reports and other features. If you use `webrtc.NewPeerConnection`
// this is enabled by default. If you are manually managing You MUST create a InterceptorRegistry
// for each PeerConnection.
i := &interceptor.Registry{}
interceptorRegistry := &interceptor.Registry{}
// Register a intervalpli factory
// This interceptor sends a PLI every 3 seconds. A PLI causes a video keyframe to be generated by the sender.
@@ -79,15 +84,15 @@ func whipHandler(w http.ResponseWriter, r *http.Request) {
if err != nil {
panic(err)
}
i.Add(intervalPliFactory)
interceptorRegistry.Add(intervalPliFactory)
// Use the default set of Interceptors
if err = webrtc.RegisterDefaultInterceptors(m, i); err != nil {
if err = webrtc.RegisterDefaultInterceptors(mediaEngine, interceptorRegistry); err != nil {
panic(err)
}
// Create the API object with the MediaEngine
api := webrtc.NewAPI(webrtc.WithMediaEngine(m), webrtc.WithInterceptorRegistry(i))
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine), webrtc.WithInterceptorRegistry(interceptorRegistry))
// Prepare the configuration
@@ -119,12 +124,12 @@ func whipHandler(w http.ResponseWriter, r *http.Request) {
})
// Send answer via HTTP Response
writeAnswer(w, peerConnection, offer, "/whip")
writeAnswer(res, peerConnection, offer, "/whip")
}
func whepHandler(w http.ResponseWriter, r *http.Request) {
func whepHandler(res http.ResponseWriter, req *http.Request) {
// Read the offer from HTTP Request
offer, err := io.ReadAll(r.Body)
offer, err := io.ReadAll(req.Body)
if err != nil {
panic(err)
}
@@ -154,10 +159,10 @@ func whepHandler(w http.ResponseWriter, r *http.Request) {
}()
// Send answer via HTTP Response
writeAnswer(w, peerConnection, offer, "/whep")
writeAnswer(res, peerConnection, offer, "/whep")
}
func writeAnswer(w http.ResponseWriter, peerConnection *webrtc.PeerConnection, offer []byte, path string) {
func writeAnswer(res http.ResponseWriter, peerConnection *webrtc.PeerConnection, offer []byte, path string) {
// Set the handler for ICE connection state
// This will notify you when the peer has connected/disconnected
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
@@ -168,7 +173,9 @@ func writeAnswer(w http.ResponseWriter, peerConnection *webrtc.PeerConnection, o
}
})
if err := peerConnection.SetRemoteDescription(webrtc.SessionDescription{Type: webrtc.SDPTypeOffer, SDP: string(offer)}); err != nil {
if err := peerConnection.SetRemoteDescription(webrtc.SessionDescription{
Type: webrtc.SDPTypeOffer, SDP: string(offer),
}); err != nil {
panic(err)
}
@@ -189,9 +196,9 @@ func writeAnswer(w http.ResponseWriter, peerConnection *webrtc.PeerConnection, o
<-gatherComplete
// WHIP+WHEP expects a Location header and a HTTP Status Code of 201
w.Header().Add("Location", path)
w.WriteHeader(http.StatusCreated)
res.Header().Add("Location", path)
res.WriteHeader(http.StatusCreated)
// Write Answer with Candidates as HTTP Response
fmt.Fprint(w, peerConnection.LocalDescription().SDP) //nolint: errcheck
fmt.Fprint(res, peerConnection.LocalDescription().SDP) //nolint: errcheck
}

View File

@@ -7,10 +7,12 @@ import (
"context"
)
// GatheringCompletePromise is a Pion specific helper function that returns a channel that is closed when gathering is complete.
// GatheringCompletePromise is a Pion specific helper function that returns a channel that is closed
// when gathering is complete.
// This function may be helpful in cases where you are unable to trickle your ICE Candidates.
//
// It is better to not use this function, and instead trickle candidates. If you use this function you will see longer connection startup times.
// It is better to not use this function, and instead trickle candidates.
// If you use this function you will see longer connection startup times.
// When the call is connected you will see no impact however.
func GatheringCompletePromise(pc *PeerConnection) (gatherComplete <-chan struct{}) {
gatheringComplete, done := context.WithCancel(context.Background())

View File

@@ -9,7 +9,7 @@ import (
"github.com/pion/ice/v4"
)
// ICECandidate represents a ice candidate
// ICECandidate represents a ice candidate.
type ICECandidate struct {
statsID string
Foundation string `json:"foundation"`
@@ -26,9 +26,12 @@ type ICECandidate struct {
SDPMLineIndex uint16 `json:"sdpMLineIndex"`
}
// Conversion for package ice
func newICECandidatesFromICE(iceCandidates []ice.Candidate, sdpMid string, sdpMLineIndex uint16) ([]ICECandidate, error) {
// Conversion for package ice.
func newICECandidatesFromICE(
iceCandidates []ice.Candidate,
sdpMid string,
sdpMLineIndex uint16,
) ([]ICECandidate, error) {
candidates := []ICECandidate{}
for _, i := range iceCandidates {
@@ -42,36 +45,36 @@ func newICECandidatesFromICE(iceCandidates []ice.Candidate, sdpMid string, sdpML
return candidates, nil
}
func newICECandidateFromICE(i ice.Candidate, sdpMid string, sdpMLineIndex uint16) (ICECandidate, error) {
typ, err := convertTypeFromICE(i.Type())
func newICECandidateFromICE(candidate ice.Candidate, sdpMid string, sdpMLineIndex uint16) (ICECandidate, error) {
typ, err := convertTypeFromICE(candidate.Type())
if err != nil {
return ICECandidate{}, err
}
protocol, err := NewICEProtocol(i.NetworkType().NetworkShort())
protocol, err := NewICEProtocol(candidate.NetworkType().NetworkShort())
if err != nil {
return ICECandidate{}, err
}
c := ICECandidate{
statsID: i.ID(),
Foundation: i.Foundation(),
Priority: i.Priority(),
Address: i.Address(),
newCandidate := ICECandidate{
statsID: candidate.ID(),
Foundation: candidate.Foundation(),
Priority: candidate.Priority(),
Address: candidate.Address(),
Protocol: protocol,
Port: uint16(i.Port()),
Component: i.Component(),
Port: uint16(candidate.Port()), //nolint:gosec // G115
Component: candidate.Component(),
Typ: typ,
TCPType: i.TCPType().String(),
TCPType: candidate.TCPType().String(),
SDPMid: sdpMid,
SDPMLineIndex: sdpMLineIndex,
}
if i.RelatedAddress() != nil {
c.RelatedAddress = i.RelatedAddress().Address
c.RelatedPort = uint16(i.RelatedAddress().Port)
if candidate.RelatedAddress() != nil {
newCandidate.RelatedAddress = candidate.RelatedAddress().Address
newCandidate.RelatedPort = uint16(candidate.RelatedAddress().Port) //nolint:gosec // G115
}
return c, nil
return newCandidate, nil
}
func (c ICECandidate) toICE() (ice.Candidate, error) {
@@ -88,6 +91,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
Foundation: c.Foundation,
Priority: c.Priority,
}
return ice.NewCandidateHost(&config)
case ICECandidateTypeSrflx:
config := ice.CandidateServerReflexiveConfig{
@@ -101,6 +105,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
RelAddr: c.RelatedAddress,
RelPort: int(c.RelatedPort),
}
return ice.NewCandidateServerReflexive(&config)
case ICECandidateTypePrflx:
config := ice.CandidatePeerReflexiveConfig{
@@ -114,6 +119,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
RelAddr: c.RelatedAddress,
RelPort: int(c.RelatedPort),
}
return ice.NewCandidatePeerReflexive(&config)
case ICECandidateTypeRelay:
config := ice.CandidateRelayConfig{
@@ -127,6 +133,7 @@ func (c ICECandidate) toICE() (ice.Candidate, error) {
RelAddr: c.RelatedAddress,
RelPort: int(c.RelatedPort),
}
return ice.NewCandidateRelay(&config)
default:
return nil, fmt.Errorf("%w: %s", errICECandidateTypeUnknown, c.Typ)
@@ -153,6 +160,7 @@ func (c ICECandidate) String() string {
if err != nil {
return fmt.Sprintf("%#v failed to convert to ICE: %s", c, err)
}
return ic.String()
}

View File

@@ -3,7 +3,7 @@
package webrtc
// ICECandidateInit is used to serialize ice candidates
// ICECandidateInit is used to serialize ice candidates.
type ICECandidateInit struct {
Candidate string `json:"candidate"`
SDPMid *string `json:"sdpMid"`

View File

@@ -5,7 +5,7 @@ package webrtc
import "fmt"
// ICECandidatePair represents an ICE Candidate pair
// ICECandidatePair represents an ICE Candidate pair.
type ICECandidatePair struct {
statsID string
Local *ICECandidate
@@ -21,9 +21,10 @@ func (p *ICECandidatePair) String() string {
}
// NewICECandidatePair returns an initialized *ICECandidatePair
// for the given pair of ICECandidate instances
// for the given pair of ICECandidate instances.
func NewICECandidatePair(local, remote *ICECandidate) *ICECandidatePair {
statsID := newICECandidatePairStatsID(local.statsID, remote.statsID)
return &ICECandidatePair{
statsID: statsID,
Local: local,

View File

@@ -13,7 +13,7 @@ import (
type ICECandidateType int
const (
// ICECandidateTypeUnknown is the enum's zero-value
// ICECandidateTypeUnknown is the enum's zero-value.
ICECandidateTypeUnknown ICECandidateType = iota
// ICECandidateTypeHost indicates that the candidate is of Host type as
@@ -51,7 +51,7 @@ const (
iceCandidateTypeRelayStr = "relay"
)
// NewICECandidateType takes a string and converts it into ICECandidateType
// NewICECandidateType takes a string and converts it into ICECandidateType.
func NewICECandidateType(raw string) (ICECandidateType, error) {
switch raw {
case iceCandidateTypeHostStr:
@@ -95,6 +95,7 @@ func getCandidateType(candidateType ice.CandidateType) (ICECandidateType, error)
default:
// NOTE: this should never happen[tm]
err := fmt.Errorf("%w: %s", errICEInvalidConvertCandidateType, candidateType.String())
return ICECandidateTypeUnknown, err
}
}
@@ -108,5 +109,6 @@ func (t ICECandidateType) MarshalText() ([]byte, error) {
func (t *ICECandidateType) UnmarshalText(b []byte) error {
var err error
*t, err = NewICECandidateType(string(b))
return err
}

View File

@@ -8,7 +8,7 @@ package webrtc
type ICEComponent int
const (
// ICEComponentUnknown is the enum's zero-value
// ICEComponentUnknown is the enum's zero-value.
ICEComponentUnknown ICEComponent = iota
// ICEComponentRTP indicates that the ICE Transport is used for RTP (or

View File

@@ -7,7 +7,7 @@ package webrtc
type ICEConnectionState int
const (
// ICEConnectionStateUnknown is the enum's zero-value
// ICEConnectionStateUnknown is the enum's zero-value.
ICEConnectionStateUnknown ICEConnectionState = iota
// ICEConnectionStateNew indicates that any of the ICETransports are
@@ -56,7 +56,7 @@ const (
iceConnectionStateClosedStr = "closed"
)
// NewICEConnectionState takes a string and converts it to ICEConnectionState
// NewICEConnectionState takes a string and converts it to ICEConnectionState.
func NewICEConnectionState(raw string) ICEConnectionState {
switch raw {
case iceConnectionStateNewStr:

View File

@@ -50,7 +50,7 @@ func (t ICECredentialType) String() string {
}
}
// UnmarshalJSON parses the JSON-encoded data and stores the result
// UnmarshalJSON parses the JSON-encoded data and stores the result.
func (t *ICECredentialType) UnmarshalJSON(b []byte) error {
var val string
if err := json.Unmarshal(b, &val); err != nil {
@@ -63,10 +63,11 @@ func (t *ICECredentialType) UnmarshalJSON(b []byte) error {
}
*t = tmp
return nil
}
// MarshalJSON returns the JSON encoding
// MarshalJSON returns the JSON encoding.
func (t ICECredentialType) MarshalJSON() ([]byte, error) {
return json.Marshal(t.String())
}

View File

@@ -70,7 +70,7 @@ func (api *API) NewICEGatherer(opts ICEGatherOptions) (*ICEGatherer, error) {
}, nil
}
func (g *ICEGatherer) createAgent() error {
func (g *ICEGatherer) createAgent() error { //nolint:cyclop
g.lock.Lock()
defer g.lock.Unlock()
@@ -149,11 +149,12 @@ func (g *ICEGatherer) createAgent() error {
}
g.agent = agent
return nil
}
// Gather ICE candidates.
func (g *ICEGatherer) Gather() error {
func (g *ICEGatherer) Gather() error { //nolint:cyclop
if err := g.createAgent(); err != nil {
return err
}
@@ -182,12 +183,13 @@ func (g *ICEGatherer) Gather() error {
sdpMid = mid
}
sdpMLineIndex := uint16(g.sdpMLineIndex.Load())
sdpMLineIndex := uint16(g.sdpMLineIndex.Load()) //nolint:gosec // G115
if candidate != nil {
c, err := newICECandidateFromICE(candidate, sdpMid, sdpMLineIndex)
if err != nil {
g.log.Warnf("Failed to convert ice.Candidate: %s", err)
return
}
onLocalCandidateHandler(&c)
@@ -200,10 +202,11 @@ func (g *ICEGatherer) Gather() error {
}); err != nil {
return err
}
return agent.GatherCandidates()
}
// set media stream identification tag and media description index for this gatherer
// set media stream identification tag and media description index for this gatherer.
func (g *ICEGatherer) setMediaStreamIdentification(mid string, mLineIndex uint16) {
g.sdpMid.Store(mid)
g.sdpMLineIndex.Store(uint32(mLineIndex))
@@ -290,7 +293,7 @@ func (g *ICEGatherer) GetLocalCandidates() ([]ICECandidate, error) {
sdpMid = mid
}
sdpMLineIndex := uint16(g.sdpMLineIndex.Load())
sdpMLineIndex := uint16(g.sdpMLineIndex.Load()) //nolint:gosec // G115
return newICECandidatesFromICE(iceCandidates, sdpMid, sdpMLineIndex)
}
@@ -301,7 +304,7 @@ func (g *ICEGatherer) OnLocalCandidate(f func(*ICECandidate)) {
g.onLocalCandidateHandler.Store(f)
}
// OnStateChange fires any time the ICEGatherer changes
// OnStateChange fires any time the ICEGatherer changes.
func (g *ICEGatherer) OnStateChange(f func(ICEGathererState)) {
g.onStateChangeHandler.Store(f)
}
@@ -322,6 +325,7 @@ func (g *ICEGatherer) setState(s ICEGathererState) {
func (g *ICEGatherer) getAgent() *ice.Agent {
g.lock.RLock()
defer g.lock.RUnlock()
return g.agent
}
@@ -339,6 +343,7 @@ func (g *ICEGatherer) collectStats(collector *statsReportCollector) {
stats, err := toICECandidatePairStats(candidatePairStats)
if err != nil {
g.log.Error(err.Error())
continue
}
@@ -363,10 +368,10 @@ func (g *ICEGatherer) collectStats(collector *statsReportCollector) {
ID: candidateStats.ID,
Type: StatsTypeLocalCandidate,
IP: candidateStats.IP,
Port: int32(candidateStats.Port),
Port: int32(candidateStats.Port), //nolint:gosec // G115, no overflow, port
Protocol: networkType.Protocol(),
CandidateType: candidateType,
Priority: int32(candidateStats.Priority),
Priority: int32(candidateStats.Priority), //nolint:gosec
URL: candidateStats.URL,
RelayProtocol: candidateStats.RelayProtocol,
Deleted: candidateStats.Deleted,
@@ -391,10 +396,10 @@ func (g *ICEGatherer) collectStats(collector *statsReportCollector) {
ID: candidateStats.ID,
Type: StatsTypeRemoteCandidate,
IP: candidateStats.IP,
Port: int32(candidateStats.Port),
Port: int32(candidateStats.Port), //nolint:gosec // G115, no overflow, port
Protocol: networkType.Protocol(),
CandidateType: candidateType,
Priority: int32(candidateStats.Priority),
Priority: int32(candidateStats.Priority), //nolint:gosec // G115
URL: candidateStats.URL,
RelayProtocol: candidateStats.RelayProtocol,
}
@@ -418,6 +423,7 @@ func (g *ICEGatherer) getSelectedCandidatePairStats() (ICECandidatePairStats, bo
stats, err := toICECandidatePairStats(selectedCandidatePairStats)
if err != nil {
g.log.Error(err.Error())
return ICECandidatePairStats{}, false
}

View File

@@ -157,7 +157,7 @@ func TestICEGatherer_AlreadyClosed(t *testing.T) {
})
}
func TestNewICEGathererSetMediaStreamIdentification(t *testing.T) {
func TestNewICEGathererSetMediaStreamIdentification(t *testing.T) { //nolint:cyclop
// Limit runtime in case of deadlocks
lim := test.TimeOut(time.Second * 20)
defer lim.Stop()

View File

@@ -11,7 +11,7 @@ import (
type ICEGathererState uint32
const (
// ICEGathererStateUnknown is the enum's zero-value
// ICEGathererStateUnknown is the enum's zero-value.
ICEGathererStateUnknown ICEGathererState = iota
// ICEGathererStateNew indicates object has been created but

View File

@@ -7,7 +7,7 @@ package webrtc
type ICEGatheringState int
const (
// ICEGatheringStateUnknown is the enum's zero-value
// ICEGatheringStateUnknown is the enum's zero-value.
ICEGatheringStateUnknown ICEGatheringState = iota
// ICEGatheringStateNew indicates that any of the ICETransports are
@@ -31,7 +31,7 @@ const (
iceGatheringStateCompleteStr = "complete"
)
// NewICEGatheringState takes a string and converts it to ICEGatheringState
// NewICEGatheringState takes a string and converts it to ICEGatheringState.
func NewICEGatheringState(raw string) ICEGatheringState {
switch raw {
case iceGatheringStateNewStr:

View File

@@ -13,7 +13,7 @@ import (
type ICEProtocol int
const (
// ICEProtocolUnknown is the enum's zero-value
// ICEProtocolUnknown is the enum's zero-value.
ICEProtocolUnknown ICEProtocol = iota
// ICEProtocolUDP indicates the URL uses a UDP transport.
@@ -29,7 +29,7 @@ const (
iceProtocolTCPStr = "tcp"
)
// NewICEProtocol takes a string and converts it to ICEProtocol
// NewICEProtocol takes a string and converts it to ICEProtocol.
func NewICEProtocol(raw string) (ICEProtocol, error) {
switch {
case strings.EqualFold(iceProtocolUDPStr, raw):

View File

@@ -8,7 +8,7 @@ package webrtc
type ICERole int
const (
// ICERoleUnknown is the enum's zero-value
// ICERoleUnknown is the enum's zero-value.
ICERoleUnknown ICERole = iota
// ICERoleControlling indicates that the ICE agent that is responsible
@@ -50,13 +50,14 @@ func (t ICERole) String() string {
}
}
// MarshalText implements encoding.TextMarshaler
// MarshalText implements encoding.TextMarshaler.
func (t ICERole) MarshalText() ([]byte, error) {
return []byte(t.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler
// UnmarshalText implements encoding.TextUnmarshaler.
func (t *ICERole) UnmarshalText(b []byte) error {
*t = newICERole(string(b))
return nil
}

View File

@@ -28,10 +28,11 @@ func (s ICEServer) parseURL(i int) (*stun.URI, error) {
func (s ICEServer) validate() error {
_, err := s.urls()
return err
}
func (s ICEServer) urls() ([]*stun.URI, error) {
func (s ICEServer) urls() ([]*stun.URI, error) { //nolint:cyclop
urls := []*stun.URI{}
for i := range s.URLs {
@@ -85,6 +86,7 @@ func iceserverUnmarshalUrls(val interface{}) (*[]string, error) {
return nil, errInvalidICEServer
}
}
return &out, nil
}
@@ -101,14 +103,15 @@ func iceserverUnmarshalOauth(val interface{}) (*OAuthCredential, error) {
if !ok {
return nil, errInvalidICEServer
}
return &OAuthCredential{
MACKey: MACKey,
AccessToken: AccessToken,
}, nil
}
func (s *ICEServer) iceserverUnmarshalFields(m map[string]interface{}) error {
if val, ok := m["urls"]; ok {
func (s *ICEServer) iceserverUnmarshalFields(fields map[string]interface{}) error { //nolint:cyclop
if val, ok := fields["urls"]; ok {
u, err := iceserverUnmarshalUrls(val)
if err != nil {
return err
@@ -118,13 +121,13 @@ func (s *ICEServer) iceserverUnmarshalFields(m map[string]interface{}) error {
s.URLs = []string{}
}
if val, ok := m["username"]; ok {
if val, ok := fields["username"]; ok {
s.Username, ok = val.(string)
if !ok {
return errInvalidICEServer
}
}
if val, ok := m["credentialType"]; ok {
if val, ok := fields["credentialType"]; ok {
ct, ok := val.(string)
if !ok {
return errInvalidICEServer
@@ -137,7 +140,7 @@ func (s *ICEServer) iceserverUnmarshalFields(m map[string]interface{}) error {
} else {
s.CredentialType = ICECredentialTypePassword
}
if val, ok := m["credential"]; ok {
if val, ok := fields["credential"]; ok {
switch s.CredentialType {
case ICECredentialTypePassword:
s.Credential = val
@@ -151,10 +154,11 @@ func (s *ICEServer) iceserverUnmarshalFields(m map[string]interface{}) error {
return errInvalidICECredentialTypeString
}
}
return nil
}
// UnmarshalJSON parses the JSON-encoded data and stores the result
// UnmarshalJSON parses the JSON-encoded data and stores the result.
func (s *ICEServer) UnmarshalJSON(b []byte) error {
var tmp interface{}
err := json.Unmarshal(b, &tmp)
@@ -164,10 +168,11 @@ func (s *ICEServer) UnmarshalJSON(b []byte) error {
if m, ok := tmp.(map[string]interface{}); ok {
return s.iceserverUnmarshalFields(m)
}
return errInvalidICEServer
}
// MarshalJSON returns the JSON encoding
// MarshalJSON returns the JSON encoding.
func (s ICEServer) MarshalJSON() ([]byte, error) {
m := make(map[string]interface{})
m["urls"] = s.URLs
@@ -178,5 +183,6 @@ func (s ICEServer) MarshalJSON() ([]byte, error) {
m["credential"] = s.Credential
}
m["credentialType"] = s.CredentialType
return json.Marshal(m)
}

View File

@@ -99,6 +99,7 @@ func TestICEServer_validate(t *testing.T) {
}
})
t.Run("JsonFailure", func(t *testing.T) {
//nolint:lll
testCases := [][]byte{
[]byte(`{"urls":"NOTAURL","username":"unittest","credential":"placeholder","credentialType":"password"}`),
[]byte(`{"urls":["turn:[2001:db8:1234:5678::1]?transport=udp"],"username":"unittest","credential":"placeholder","credentialType":"invalid"}`),

View File

@@ -36,7 +36,6 @@ type ICETransport struct {
conn *ice.Conn
mux *mux.Mux
ctx context.Context
ctxCancel func()
loggerFactory logging.LoggerFactory
@@ -45,7 +44,7 @@ type ICETransport struct {
}
// GetSelectedCandidatePair returns the selected candidate pair on which packets are sent
// if there is no selected pair nil is returned
// if there is no selected pair nil is returned.
func (t *ICETransport) GetSelectedCandidatePair() (*ICECandidatePair, error) {
agent := t.gatherer.getAgent()
if agent == nil {
@@ -71,7 +70,7 @@ func (t *ICETransport) GetSelectedCandidatePair() (*ICECandidatePair, error) {
}
// GetSelectedCandidatePairStats returns the selected candidate pair stats on which packets are sent
// if there is no selected pair empty stats, false is returned to indicate stats not available
// if there is no selected pair empty stats, false is returned to indicate stats not available.
func (t *ICETransport) GetSelectedCandidatePairStats() (ICECandidatePairStats, bool) {
return t.gatherer.getSelectedCandidatePairStats()
}
@@ -84,11 +83,12 @@ func NewICETransport(gatherer *ICEGatherer, loggerFactory logging.LoggerFactory)
log: loggerFactory.NewLogger("ortc"),
}
iceTransport.setState(ICETransportStateNew)
return iceTransport
}
// Start incoming connectivity checks based on its configured role.
func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *ICERole) error {
func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *ICERole) error { //nolint:cyclop
t.lock.Lock()
defer t.lock.Unlock()
@@ -121,6 +121,7 @@ func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *
candidates, err := newICECandidatesFromICE([]ice.Candidate{local, remote}, "", 0)
if err != nil {
t.log.Warnf("%w: %s", errICECandiatesCoversionFailed, err)
return
}
t.onSelectedCandidatePairChange(NewICECandidatePair(&candidates[0], &candidates[1]))
@@ -134,7 +135,8 @@ func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *
}
t.role = *role
t.ctx, t.ctxCancel = context.WithCancel(context.Background())
ctx, ctxCancel := context.WithCancel(context.Background())
t.ctxCancel = ctxCancel
// Drop the lock here to allow ICE candidates to be
// added so that the agent can complete a connection
@@ -144,12 +146,12 @@ func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *
var err error
switch *role {
case ICERoleControlling:
iceConn, err = agent.Dial(t.ctx,
iceConn, err = agent.Dial(ctx,
params.UsernameFragment,
params.Password)
case ICERoleControlled:
iceConn, err = agent.Accept(t.ctx,
iceConn, err = agent.Accept(ctx,
params.UsernameFragment,
params.Password)
@@ -171,7 +173,7 @@ func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *
config := mux.Config{
Conn: t.conn,
BufferSize: int(t.gatherer.api.settingEngine.getReceiveMTU()),
BufferSize: int(t.gatherer.api.settingEngine.getReceiveMTU()), //nolint:gosec // G115
LoggerFactory: t.loggerFactory,
}
t.mux = mux.NewMux(config)
@@ -180,7 +182,7 @@ func (t *ICETransport) Start(gatherer *ICEGatherer, params ICEParameters, role *
}
// restart is not exposed currently because ORTC has users create a whole new ICETransport
// so for now lets keep it private so we don't cause ORTC users to depend on non-standard APIs
// so for now lets keep it private so we don't cause ORTC users to depend on non-standard APIs.
func (t *ICETransport) restart() error {
t.lock.Lock()
defer t.lock.Unlock()
@@ -190,9 +192,13 @@ func (t *ICETransport) restart() error {
return fmt.Errorf("%w: unable to restart ICETransport", errICEAgentNotExist)
}
if err := agent.Restart(t.gatherer.api.settingEngine.candidates.UsernameFragment, t.gatherer.api.settingEngine.candidates.Password); err != nil {
if err := agent.Restart(
t.gatherer.api.settingEngine.candidates.UsernameFragment,
t.gatherer.api.settingEngine.candidates.Password,
); err != nil {
return err
}
return t.gatherer.Gather()
}
@@ -229,18 +235,21 @@ func (t *ICETransport) stop(shouldGracefullyClose bool) error {
closeErrs = append(closeErrs, gatherer.GracefulClose())
}
closeErrs = append(closeErrs, mux.Close())
return util.FlattenErrs(closeErrs)
} else if gatherer != nil {
if shouldGracefullyClose {
return gatherer.GracefulClose()
}
return gatherer.Close()
}
return nil
}
// OnSelectedCandidatePairChange sets a handler that is invoked when a new
// ICE candidate pair is selected
// ICE candidate pair is selected.
func (t *ICETransport) OnSelectedCandidatePairChange(f func(*ICECandidatePair)) {
t.onSelectedCandidatePairChangeHandler.Store(f)
}
@@ -308,8 +317,8 @@ func (t *ICETransport) AddRemoteCandidate(remoteCandidate *ICECandidate) error {
defer t.lock.RUnlock()
var (
c ice.Candidate
err error
candidate ice.Candidate
err error
)
if err = t.ensureGatherer(); err != nil {
@@ -317,7 +326,7 @@ func (t *ICETransport) AddRemoteCandidate(remoteCandidate *ICECandidate) error {
}
if remoteCandidate != nil {
if c, err = remoteCandidate.toICE(); err != nil {
if candidate, err = remoteCandidate.toICE(); err != nil {
return err
}
}
@@ -327,7 +336,7 @@ func (t *ICETransport) AddRemoteCandidate(remoteCandidate *ICECandidate) error {
return fmt.Errorf("%w: unable to add remote candidates", errICEAgentNotExist)
}
return agent.AddRemoteCandidate(c)
return agent.AddRemoteCandidate(candidate)
}
// State returns the current ice transport state.
@@ -335,6 +344,7 @@ func (t *ICETransport) State() ICETransportState {
if v, ok := t.state.Load().(ICETransportState); ok {
return v
}
return ICETransportState(0)
}
@@ -355,6 +365,7 @@ func (t *ICETransport) setState(i ICETransportState) {
func (t *ICETransport) newEndpoint(f mux.MatchFunc) *mux.Endpoint {
t.lock.Lock()
defer t.lock.Unlock()
return t.mux.NewEndpoint(f)
}

View File

@@ -11,7 +11,7 @@ import (
// permitted candidates. Only these candidates are used for connectivity checks.
type ICETransportPolicy int
// ICEGatherPolicy is the ORTC equivalent of ICETransportPolicy
// ICEGatherPolicy is the ORTC equivalent of ICETransportPolicy.
type ICEGatherPolicy = ICETransportPolicy
const (
@@ -29,7 +29,7 @@ const (
iceTransportPolicyAllStr = "all"
)
// NewICETransportPolicy takes a string and converts it to ICETransportPolicy
// NewICETransportPolicy takes a string and converts it to ICETransportPolicy.
func NewICETransportPolicy(raw string) ICETransportPolicy {
switch raw {
case iceTransportPolicyRelayStr:
@@ -50,17 +50,18 @@ func (t ICETransportPolicy) String() string {
}
}
// UnmarshalJSON parses the JSON-encoded data and stores the result
// UnmarshalJSON parses the JSON-encoded data and stores the result.
func (t *ICETransportPolicy) UnmarshalJSON(b []byte) error {
var val string
if err := json.Unmarshal(b, &val); err != nil {
return err
}
*t = NewICETransportPolicy(val)
return nil
}
// MarshalJSON returns the JSON encoding
// MarshalJSON returns the JSON encoding.
func (t ICETransportPolicy) MarshalJSON() ([]byte, error) {
return json.Marshal(t.String())
}

View File

@@ -9,7 +9,7 @@ import "github.com/pion/ice/v4"
type ICETransportState int
const (
// ICETransportStateUnknown is the enum's zero-value
// ICETransportStateUnknown is the enum's zero-value.
ICETransportStateUnknown ICETransportState = iota
// ICETransportStateNew indicates the ICETransport is waiting
@@ -143,13 +143,14 @@ func (c ICETransportState) toICE() ice.ConnectionState {
}
}
// MarshalText implements encoding.TextMarshaler
// MarshalText implements encoding.TextMarshaler.
func (c ICETransportState) MarshalText() ([]byte, error) {
return []byte(c.String()), nil
}
// UnmarshalText implements encoding.TextUnmarshaler
// UnmarshalText implements encoding.TextUnmarshaler.
func (c *ICETransportState) UnmarshalText(b []byte) error {
*c = newICETransportState(string(b))
return nil
}

View File

@@ -37,7 +37,7 @@ func RegisterDefaultInterceptors(mediaEngine *MediaEngine, interceptorRegistry *
return ConfigureTWCCSender(mediaEngine, interceptorRegistry)
}
// ConfigureRTCPReports will setup everything necessary for generating Sender and Receiver Reports
// ConfigureRTCPReports will setup everything necessary for generating Sender and Receiver Reports.
func ConfigureRTCPReports(interceptorRegistry *interceptor.Registry) error {
reciver, err := report.NewReceiverInterceptor()
if err != nil {
@@ -51,6 +51,7 @@ func ConfigureRTCPReports(interceptorRegistry *interceptor.Registry) error {
interceptorRegistry.Add(reciver)
interceptorRegistry.Add(sender)
return nil
}
@@ -70,17 +71,22 @@ func ConfigureNack(mediaEngine *MediaEngine, interceptorRegistry *interceptor.Re
mediaEngine.RegisterFeedback(RTCPFeedback{Type: "nack", Parameter: "pli"}, RTPCodecTypeVideo)
interceptorRegistry.Add(responder)
interceptorRegistry.Add(generator)
return nil
}
// ConfigureTWCCHeaderExtensionSender will setup everything necessary for adding
// a TWCC header extension to outgoing RTP packets. This will allow the remote peer to generate TWCC reports.
func ConfigureTWCCHeaderExtensionSender(mediaEngine *MediaEngine, interceptorRegistry *interceptor.Registry) error {
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeVideo); err != nil {
if err := mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeVideo,
); err != nil {
return err
}
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeAudio); err != nil {
if err := mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeAudio,
); err != nil {
return err
}
@@ -90,6 +96,7 @@ func ConfigureTWCCHeaderExtensionSender(mediaEngine *MediaEngine, interceptorReg
}
interceptorRegistry.Add(i)
return nil
}
@@ -97,12 +104,16 @@ func ConfigureTWCCHeaderExtensionSender(mediaEngine *MediaEngine, interceptorReg
// This must be called after registering codecs with the MediaEngine.
func ConfigureTWCCSender(mediaEngine *MediaEngine, interceptorRegistry *interceptor.Registry) error {
mediaEngine.RegisterFeedback(RTCPFeedback{Type: TypeRTCPFBTransportCC}, RTPCodecTypeVideo)
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeVideo); err != nil {
if err := mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeVideo,
); err != nil {
return err
}
mediaEngine.RegisterFeedback(RTCPFeedback{Type: TypeRTCPFBTransportCC}, RTPCodecTypeAudio)
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeAudio); err != nil {
if err := mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{URI: sdp.TransportCCURI}, RTPCodecTypeAudio,
); err != nil {
return err
}
@@ -112,6 +123,7 @@ func ConfigureTWCCSender(mediaEngine *MediaEngine, interceptorRegistry *intercep
}
interceptorRegistry.Add(generator)
return nil
}
@@ -125,20 +137,27 @@ func ConfigureCongestionControlFeedback(mediaEngine *MediaEngine, interceptorReg
return err
}
interceptorRegistry.Add(generator)
return nil
}
// ConfigureSimulcastExtensionHeaders enables the RTP Extension Headers needed for Simulcast
// ConfigureSimulcastExtensionHeaders enables the RTP Extension Headers needed for Simulcast.
func ConfigureSimulcastExtensionHeaders(mediaEngine *MediaEngine) error {
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.SDESMidURI}, RTPCodecTypeVideo); err != nil {
if err := mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{URI: sdp.SDESMidURI}, RTPCodecTypeVideo,
); err != nil {
return err
}
if err := mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.SDESRTPStreamIDURI}, RTPCodecTypeVideo); err != nil {
if err := mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{URI: sdp.SDESRTPStreamIDURI}, RTPCodecTypeVideo,
); err != nil {
return err
}
return mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.SDESRepairRTPStreamIDURI}, RTPCodecTypeVideo)
return mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{URI: sdp.SDESRepairRTPStreamIDURI}, RTPCodecTypeVideo,
)
}
type interceptorToTrackLocalWriter struct{ interceptor atomic.Value } // interceptor.RTPWriter }
@@ -160,8 +179,14 @@ func (i *interceptorToTrackLocalWriter) Write(b []byte) (int, error) {
return i.WriteRTP(&packet.Header, packet.Payload)
}
// nolint: unparam
func createStreamInfo(id string, ssrc, ssrcRTX, ssrcFEC SSRC, payloadType, payloadTypeRTX, payloadTypeFEC PayloadType, codec RTPCodecCapability, webrtcHeaderExtensions []RTPHeaderExtensionParameter) *interceptor.StreamInfo {
//nolint:unparam
func createStreamInfo(
id string,
ssrc, ssrcRTX, ssrcFEC SSRC,
payloadType, payloadTypeRTX, payloadTypeFEC PayloadType,
codec RTPCodecCapability,
webrtcHeaderExtensions []RTPHeaderExtensionParameter,
) *interceptor.StreamInfo {
headerExtensions := make([]interceptor.RTPHeaderExtension, 0, len(webrtcHeaderExtensions))
for _, h := range webrtcHeaderExtensions {
headerExtensions = append(headerExtensions, interceptor.RTPHeaderExtension{ID: h.ID, URI: h.URI})

View File

@@ -26,7 +26,7 @@ import (
// E2E test of the features of Interceptors
// * Assert an extension can be set on an outbound packet
// * Assert an extension can be read on an outbound packet
// * Assert that attributes set by an interceptor are returned to the Reader
// * Assert that attributes set by an interceptor are returned to the Reader.
func TestPeerConnection_Interceptor(t *testing.T) {
to := test.TimeOut(time.Second * 20)
defer to.Stop()
@@ -40,14 +40,16 @@ func TestPeerConnection_Interceptor(t *testing.T) {
NewInterceptorFn: func(_ string) (interceptor.Interceptor, error) {
return &mock_interceptor.Interceptor{
BindLocalStreamFn: func(_ *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
return interceptor.RTPWriterFunc(func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
// set extension on outgoing packet
header.Extension = true
header.ExtensionProfile = 0xBEDE
assert.NoError(t, header.SetExtension(2, []byte("foo")))
return interceptor.RTPWriterFunc(
func(header *rtp.Header, payload []byte, attributes interceptor.Attributes) (int, error) {
// set extension on outgoing packet
header.Extension = true
header.ExtensionProfile = 0xBEDE
assert.NoError(t, header.SetExtension(2, []byte("foo")))
return writer.Write(header, payload, attributes)
})
return writer.Write(header, payload, attributes)
},
)
},
BindRemoteStreamFn: func(_ *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
return interceptor.RTPReaderFunc(func(b []byte, a interceptor.Attributes) (int, interceptor.Attributes, error) {
@@ -56,6 +58,7 @@ func TestPeerConnection_Interceptor(t *testing.T) {
}
a.Set("attribute", "value")
return reader.Read(b, a)
})
},
@@ -108,7 +111,7 @@ func TestPeerConnection_Interceptor(t *testing.T) {
closePairNow(t, offerer, answerer)
}
func Test_Interceptor_BindUnbind(t *testing.T) {
func Test_Interceptor_BindUnbind(t *testing.T) { //nolint:cyclop
lim := test.TimeOut(time.Second * 10)
defer lim.Stop()
@@ -127,14 +130,17 @@ func Test_Interceptor_BindUnbind(t *testing.T) {
mockInterceptor := &mock_interceptor.Interceptor{
BindRTCPReaderFn: func(reader interceptor.RTCPReader) interceptor.RTCPReader {
atomic.AddUint32(&cntBindRTCPReader, 1)
return reader
},
BindRTCPWriterFn: func(writer interceptor.RTCPWriter) interceptor.RTCPWriter {
atomic.AddUint32(&cntBindRTCPWriter, 1)
return writer
},
BindLocalStreamFn: func(_ *interceptor.StreamInfo, writer interceptor.RTPWriter) interceptor.RTPWriter {
atomic.AddUint32(&cntBindLocalStream, 1)
return writer
},
UnbindLocalStreamFn: func(*interceptor.StreamInfo) {
@@ -142,6 +148,7 @@ func Test_Interceptor_BindUnbind(t *testing.T) {
},
BindRemoteStreamFn: func(_ *interceptor.StreamInfo, reader interceptor.RTPReader) interceptor.RTPReader {
atomic.AddUint32(&cntBindRemoteStream, 1)
return reader
},
UnbindRemoteStreamFn: func(_ *interceptor.StreamInfo) {
@@ -149,6 +156,7 @@ func Test_Interceptor_BindUnbind(t *testing.T) {
},
CloseFn: func() error {
atomic.AddUint32(&cntClose, 1)
return nil
},
}
@@ -225,6 +233,7 @@ func Test_InterceptorRegistry_Build(t *testing.T) {
ir.Add(&mock_interceptor.Factory{
NewInterceptorFn: func(_ string) (interceptor.Interceptor, error) {
registryBuildCount++
return &interceptor.NoOp{}, nil
},
})
@@ -274,6 +283,7 @@ func Test_Interceptor_ZeroSSRC(t *testing.T) {
if nonMediaBandwidthProbe, ok := answerer.nonMediaBandwidthProbe.Load().(*RTPReceiver); ok {
assert.Equal(t, len(nonMediaBandwidthProbe.Tracks()), 1)
close(probeReceiverCreated)
return
}
}
@@ -301,16 +311,18 @@ func TestInterceptorNack(t *testing.T) {
t.Run("NoNack", func(t *testing.T) { testInterceptorNack(t, false) })
}
func testInterceptorNack(t *testing.T, requestNack bool) {
func testInterceptorNack(t *testing.T, requestNack bool) { //nolint:cyclop
t.Helper()
const numPackets = 20
ir := interceptor.Registry{}
m := MediaEngine{}
mediaEngine := MediaEngine{}
var feedback []RTCPFeedback
if requestNack {
feedback = append(feedback, RTCPFeedback{"nack", ""})
}
err := m.RegisterCodec(
err := mediaEngine.RegisterCodec(
RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{
"video/VP8", 90000, 0,
@@ -323,7 +335,7 @@ func testInterceptorNack(t *testing.T, requestNack bool) {
)
assert.NoError(t, err)
api := NewAPI(
WithMediaEngine(&m),
WithMediaEngine(&mediaEngine),
WithInterceptorRegistry(&ir),
)
@@ -396,7 +408,7 @@ func testInterceptorNack(t *testing.T, requestNack bool) {
}
p, _, err2 := track2.ReadRTP()
assert.NoError(t, err2)
assert.Equal(t, p.SequenceNumber, uint16(i))
assert.Equal(t, p.SequenceNumber, uint16(i)) //nolint:gosec //G115
}
close(done)
})
@@ -411,8 +423,8 @@ func testInterceptorNack(t *testing.T, requestNack bool) {
p.Version = 2
p.Marker = true
p.PayloadType = 96
p.SequenceNumber = uint16(i)
p.Timestamp = uint32(i * 90000 / 50)
p.SequenceNumber = uint16(i) //nolint:gosec // G115
p.Timestamp = uint32(i * 90000 / 50) ///nolint:gosec // G115
p.Payload = []byte{42}
err2 := track1.WriteRTP(&p)
assert.NoError(t, err2)

View File

@@ -37,5 +37,6 @@ func (h *av1FMTP) Match(b FMTP) bool {
func (h *av1FMTP) Parameter(key string) (string, bool) {
v, ok := h.parameters[key]
return v, ok
}

View File

@@ -25,7 +25,7 @@ func parseParameters(line string) map[string]string {
}
// FMTP interface for implementing custom
// FMTP parsers based on MimeType
// FMTP parsers based on MimeType.
type FMTP interface {
// MimeType returns the MimeType associated with
// the fmtp
@@ -38,36 +38,36 @@ type FMTP interface {
Parameter(key string) (string, bool)
}
// Parse parses an fmtp string based on the MimeType
// Parse parses an fmtp string based on the MimeType.
func Parse(mimeType, line string) FMTP {
var f FMTP
var fmtp FMTP
parameters := parseParameters(line)
switch {
case strings.EqualFold(mimeType, "video/h264"):
f = &h264FMTP{
fmtp = &h264FMTP{
parameters: parameters,
}
case strings.EqualFold(mimeType, "video/vp9"):
f = &vp9FMTP{
fmtp = &vp9FMTP{
parameters: parameters,
}
case strings.EqualFold(mimeType, "video/av1"):
f = &av1FMTP{
fmtp = &av1FMTP{
parameters: parameters,
}
default:
f = &genericFMTP{
fmtp = &genericFMTP{
mimeType: mimeType,
parameters: parameters,
}
}
return f
return fmtp
}
type genericFMTP struct {
@@ -80,24 +80,24 @@ func (g *genericFMTP) MimeType() string {
}
// Match returns true if g and b are compatible fmtp descriptions
// The generic implementation is used for MimeTypes that are not defined
// The generic implementation is used for MimeTypes that are not defined.
func (g *genericFMTP) Match(b FMTP) bool {
c, ok := b.(*genericFMTP)
fmtp, ok := b.(*genericFMTP)
if !ok {
return false
}
if !strings.EqualFold(g.mimeType, c.MimeType()) {
if !strings.EqualFold(g.mimeType, fmtp.MimeType()) {
return false
}
for k, v := range g.parameters {
if vb, ok := c.parameters[k]; ok && !strings.EqualFold(vb, v) {
if vb, ok := fmtp.parameters[k]; ok && !strings.EqualFold(vb, v) {
return false
}
}
for k, v := range c.parameters {
for k, v := range fmtp.parameters {
if va, ok := g.parameters[k]; ok && !strings.EqualFold(va, v) {
return false
}
@@ -108,5 +108,6 @@ func (g *genericFMTP) Match(b FMTP) bool {
func (g *genericFMTP) Parameter(key string) (string, bool) {
v, ok := g.parameters[key]
return v, ok
}

View File

@@ -127,7 +127,7 @@ func TestParse(t *testing.T) {
}
}
func TestMatch(t *testing.T) {
func TestMatch(t *testing.T) { //nolint:maintidx
consistString := map[bool]string{true: "consist", false: "inconsist"}
for _, ca := range []struct {

View File

@@ -16,6 +16,7 @@ func profileLevelIDMatches(a, b string) bool {
if err != nil || len(bb) < 2 {
return false
}
return aa[0] == bb[0] && aa[1] == bb[1]
}
@@ -41,7 +42,7 @@ func (h *h264FMTP) MimeType() string {
// apply for the level part of profile-level-id and does not apply
// for the other stream properties and capability parameters.
func (h *h264FMTP) Match(b FMTP) bool {
c, ok := b.(*h264FMTP)
fmtp, ok := b.(*h264FMTP)
if !ok {
return false
}
@@ -51,7 +52,7 @@ func (h *h264FMTP) Match(b FMTP) bool {
if !hok {
return false
}
cpmode, cok := c.parameters["packetization-mode"]
cpmode, cok := fmtp.parameters["packetization-mode"]
if !cok {
return false
}
@@ -66,7 +67,7 @@ func (h *h264FMTP) Match(b FMTP) bool {
return false
}
cplid, cok := c.parameters["profile-level-id"]
cplid, cok := fmtp.parameters["profile-level-id"]
if !cok {
return false
}
@@ -80,5 +81,6 @@ func (h *h264FMTP) Match(b FMTP) bool {
func (h *h264FMTP) Parameter(key string) (string, bool) {
v, ok := h.parameters[key]
return v, ok
}

View File

@@ -37,5 +37,6 @@ func (h *vp9FMTP) Match(b FMTP) bool {
func (h *vp9FMTP) Parameter(key string) (string, bool) {
v, ok := h.parameters[key]
return v, ok
}

View File

@@ -20,7 +20,7 @@ type Endpoint struct {
onClose func()
}
// Close unregisters the endpoint from the Mux
// Close unregisters the endpoint from the Mux.
func (e *Endpoint) Close() (err error) {
if e.onClose != nil {
e.onClose()
@@ -31,6 +31,7 @@ func (e *Endpoint) Close() (err error) {
}
e.mux.RemoveEndpoint(e)
return nil
}
@@ -39,19 +40,20 @@ func (e *Endpoint) close() error {
}
// Read reads a packet of len(p) bytes from the underlying conn
// that are matched by the associated MuxFunc
// that are matched by the associated MuxFunc.
func (e *Endpoint) Read(p []byte) (int, error) {
return e.buffer.Read(p)
}
// ReadFrom reads a packet of len(p) bytes from the underlying conn
// that are matched by the associated MuxFunc
// that are matched by the associated MuxFunc.
func (e *Endpoint) ReadFrom(p []byte) (int, net.Addr, error) {
i, err := e.Read(p)
return i, nil, err
}
// Write writes len(p) bytes to the underlying conn
// Write writes len(p) bytes to the underlying conn.
func (e *Endpoint) Write(p []byte) (int, error) {
n, err := e.mux.nextConn.Write(p)
if errors.Is(err, ice.ErrNoCandidatePairs) {
@@ -63,38 +65,38 @@ func (e *Endpoint) Write(p []byte) (int, error) {
return n, err
}
// WriteTo writes len(p) bytes to the underlying conn
// WriteTo writes len(p) bytes to the underlying conn.
func (e *Endpoint) WriteTo(p []byte, _ net.Addr) (int, error) {
return e.Write(p)
}
// LocalAddr is a stub
// LocalAddr is a stub.
func (e *Endpoint) LocalAddr() net.Addr {
return e.mux.nextConn.LocalAddr()
}
// RemoteAddr is a stub
// RemoteAddr is a stub.
func (e *Endpoint) RemoteAddr() net.Addr {
return e.mux.nextConn.RemoteAddr()
}
// SetDeadline is a stub
// SetDeadline is a stub.
func (e *Endpoint) SetDeadline(time.Time) error {
return nil
}
// SetReadDeadline is a stub
// SetReadDeadline is a stub.
func (e *Endpoint) SetReadDeadline(time.Time) error {
return nil
}
// SetWriteDeadline is a stub
// SetWriteDeadline is a stub.
func (e *Endpoint) SetWriteDeadline(time.Time) error {
return nil
}
// SetOnClose is a user set callback that
// will be executed when `Close` is called
// will be executed when `Close` is called.
func (e *Endpoint) SetOnClose(onClose func()) {
e.onClose = onClose
}

View File

@@ -19,19 +19,19 @@ const (
// The maximum amount of data that can be buffered before returning errors.
maxBufferSize = 1000 * 1000 // 1MB
// How many total pending packets can be cached
// How many total pending packets can be cached.
maxPendingPackets = 15
)
// Config collects the arguments to mux.Mux construction into
// a single structure
// a single structure.
type Config struct {
Conn net.Conn
BufferSize int
LoggerFactory logging.LoggerFactory
}
// Mux allows multiplexing
// Mux allows multiplexing.
type Mux struct {
nextConn net.Conn
bufferSize int
@@ -45,9 +45,9 @@ type Mux struct {
log logging.LeveledLogger
}
// NewMux creates a new Mux
// NewMux creates a new Mux.
func NewMux(config Config) *Mux {
m := &Mux{
mux := &Mux{
nextConn: config.Conn,
endpoints: make(map[*Endpoint]MatchFunc),
bufferSize: config.BufferSize,
@@ -55,31 +55,31 @@ func NewMux(config Config) *Mux {
log: config.LoggerFactory.NewLogger("mux"),
}
go m.readLoop()
go mux.readLoop()
return m
return mux
}
// NewEndpoint creates a new Endpoint
func (m *Mux) NewEndpoint(f MatchFunc) *Endpoint {
e := &Endpoint{
// NewEndpoint creates a new Endpoint.
func (m *Mux) NewEndpoint(matchFunc MatchFunc) *Endpoint {
endpoint := &Endpoint{
mux: m,
buffer: packetio.NewBuffer(),
}
// Set a maximum size of the buffer in bytes.
e.buffer.SetLimitSize(maxBufferSize)
endpoint.buffer.SetLimitSize(maxBufferSize)
m.lock.Lock()
m.endpoints[e] = f
m.endpoints[endpoint] = matchFunc
m.lock.Unlock()
go m.handlePendingPackets(e, f)
go m.handlePendingPackets(endpoint, matchFunc)
return e
return endpoint
}
// RemoveEndpoint removes an endpoint from the Mux
// RemoveEndpoint removes an endpoint from the Mux.
func (m *Mux) RemoveEndpoint(e *Endpoint) {
m.lock.Lock()
defer m.lock.Unlock()
@@ -92,6 +92,7 @@ func (m *Mux) Close() error {
for e := range m.endpoints {
if err := e.close(); err != nil {
m.lock.Unlock()
return err
}
@@ -124,9 +125,11 @@ func (m *Mux) readLoop() {
return
case errors.Is(err, io.ErrShortBuffer), errors.Is(err, packetio.ErrTimeout):
m.log.Errorf("mux: failed to read from packetio.Buffer %s", err.Error())
continue
case err != nil:
m.log.Errorf("mux: ending readLoop packetio.Buffer error %s", err.Error())
return
}
@@ -136,6 +139,7 @@ func (m *Mux) readLoop() {
return
}
m.log.Errorf("mux: ending readLoop dispatch error %s", err.Error())
return
}
}
@@ -144,6 +148,7 @@ func (m *Mux) readLoop() {
func (m *Mux) dispatch(buf []byte) error {
if len(buf) == 0 {
m.log.Warnf("Warning: mux: unable to dispatch zero length packet")
return nil
}
@@ -153,6 +158,7 @@ func (m *Mux) dispatch(buf []byte) error {
for e, f := range m.endpoints {
if f(buf) {
endpoint = e
break
}
}
@@ -161,12 +167,21 @@ func (m *Mux) dispatch(buf []byte) error {
if !m.isClosed {
if len(m.pendingPackets) >= maxPendingPackets {
m.log.Warnf("Warning: mux: no endpoint for packet starting with %d, not adding to queue size(%d)", buf[0], len(m.pendingPackets))
m.log.Warnf(
"Warning: mux: no endpoint for packet starting with %d, not adding to queue size(%d)",
buf[0], //nolint:gosec // G602, false positive?
len(m.pendingPackets),
)
} else {
m.log.Warnf("Warning: mux: no endpoint for packet starting with %d, adding to queue size(%d)", buf[0], len(m.pendingPackets))
m.log.Warnf(
"Warning: mux: no endpoint for packet starting with %d, adding to queue size(%d)",
buf[0], //nolint:gosec // G602, false positive?
len(m.pendingPackets),
)
m.pendingPackets = append(m.pendingPackets, append([]byte{}, buf...))
}
}
return nil
}
@@ -176,6 +191,7 @@ func (m *Mux) dispatch(buf []byte) error {
// Expected when bytes are received faster than the endpoint can process them (#2152, #2180)
if errors.Is(err, packetio.ErrFull) {
m.log.Infof("mux: endpoint buffer is full, dropping packet")
return nil
}
@@ -193,7 +209,7 @@ func (m *Mux) handlePendingPackets(endpoint *Endpoint, matchFunc MatchFunc) {
m.log.Warnf("Warning: mux: error writing packet to endpoint from pending queue: %s", err)
}
} else {
pendingPackets = append(pendingPackets, buf)
pendingPackets = append(pendingPackets, buf) //nolint:makezero // todo fix
}
}
m.pendingPackets = pendingPackets

View File

@@ -22,13 +22,13 @@ func TestNoEndpoints(t *testing.T) {
ca, cb := net.Pipe()
require.NoError(t, cb.Close())
m := NewMux(Config{
mux := NewMux(Config{
Conn: ca,
BufferSize: testPipeBufferSize,
LoggerFactory: logging.NewDefaultLoggerFactory(),
})
require.NoError(t, m.dispatch(make([]byte, 1)))
require.NoError(t, m.Close())
require.NoError(t, mux.dispatch(make([]byte, 1)))
require.NoError(t, mux.Close())
require.NoError(t, ca.Close())
}
@@ -37,7 +37,7 @@ type muxErrorConnReadResult struct {
data []byte
}
// muxErrorConn
// muxErrorConn.
type muxErrorConn struct {
net.Conn
readResults []muxErrorConnReadResult
@@ -49,6 +49,7 @@ func (m *muxErrorConn) Read(b []byte) (n int, err error) {
n = len(m.readResults[0].data)
m.readResults = m.readResults[1:]
return
}
@@ -81,13 +82,13 @@ func TestNonFatalRead(t *testing.T) {
{io.EOF, nil},
}}
m := NewMux(Config{
mux := NewMux(Config{
Conn: conn,
BufferSize: testPipeBufferSize,
LoggerFactory: logging.NewDefaultLoggerFactory(),
})
e := m.NewEndpoint(MatchAll)
e := mux.NewEndpoint(MatchAll)
buff := make([]byte, testPipeBufferSize)
n, err := e.Read(buff)
@@ -98,24 +99,25 @@ func TestNonFatalRead(t *testing.T) {
require.NoError(t, err)
require.Equal(t, buff[:n], expectedData)
<-m.closedCh
require.NoError(t, m.Close())
<-mux.closedCh
require.NoError(t, mux.Close())
require.NoError(t, ca.Close())
}
// If a endpoint returns packetio.ErrFull it is a non-fatal error and shouldn't cause
// the mux to be destroyed
// pion/webrtc#2180
// .
func TestNonFatalDispatch(t *testing.T) {
in, out := net.Pipe()
m := NewMux(Config{
mux := NewMux(Config{
Conn: out,
LoggerFactory: logging.NewDefaultLoggerFactory(),
BufferSize: 1500,
})
e := m.NewEndpoint(MatchSRTP)
e := mux.NewEndpoint(MatchSRTP)
e.buffer.SetLimitSize(1)
for i := 0; i <= 25; i++ {
@@ -124,19 +126,19 @@ func TestNonFatalDispatch(t *testing.T) {
require.NoError(t, err)
}
require.NoError(t, m.Close())
require.NoError(t, mux.Close())
require.NoError(t, in.Close())
require.NoError(t, out.Close())
}
func BenchmarkDispatch(b *testing.B) {
m := &Mux{
mux := &Mux{
endpoints: make(map[*Endpoint]MatchFunc),
log: logging.NewDefaultLoggerFactory().NewLogger("mux"),
}
e := m.NewEndpoint(MatchSRTP)
m.NewEndpoint(MatchSRTCP)
endpoint := mux.NewEndpoint(MatchSRTP)
mux.NewEndpoint(MatchSRTCP)
buf := []byte{128, 1, 2, 3, 4}
buf2 := make([]byte, 1200)
@@ -144,11 +146,11 @@ func BenchmarkDispatch(b *testing.B) {
b.StartTimer()
for i := 0; i < b.N; i++ {
err := m.dispatch(buf)
err := mux.dispatch(buf)
if err != nil {
b.Errorf("dispatch: %v", err)
}
_, err = e.buffer.Read(buf2)
_, err = endpoint.buffer.Read(buf2)
if err != nil {
b.Errorf("read: %v", err)
}
@@ -158,22 +160,22 @@ func BenchmarkDispatch(b *testing.B) {
func TestPendingQueue(t *testing.T) {
factory := logging.NewDefaultLoggerFactory()
factory.DefaultLogLevel = logging.LogLevelDebug
m := &Mux{
mux := &Mux{
endpoints: make(map[*Endpoint]MatchFunc),
log: factory.NewLogger("mux"),
}
// Assert empty packets don't end up in queue
require.NoError(t, m.dispatch([]byte{}))
require.Equal(t, len(m.pendingPackets), 0)
require.NoError(t, mux.dispatch([]byte{}))
require.Equal(t, len(mux.pendingPackets), 0)
// Test Happy Case
inBuffer := []byte{20, 1, 2, 3, 4}
outBuffer := make([]byte, len(inBuffer))
require.NoError(t, m.dispatch(inBuffer))
require.NoError(t, mux.dispatch(inBuffer))
endpoint := m.NewEndpoint(MatchDTLS)
endpoint := mux.NewEndpoint(MatchDTLS)
require.NotNil(t, endpoint)
_, err := endpoint.Read(outBuffer)
@@ -183,7 +185,7 @@ func TestPendingQueue(t *testing.T) {
// Assert limit on pendingPackets
for i := 0; i <= 100; i++ {
require.NoError(t, m.dispatch([]byte{64, 65, 66}))
require.NoError(t, mux.dispatch([]byte{64, 65, 66}))
}
require.Equal(t, len(m.pendingPackets), maxPendingPackets)
require.Equal(t, len(mux.pendingPackets), maxPendingPackets)
}

View File

@@ -3,20 +3,21 @@
package mux
// MatchFunc allows custom logic for mapping packets to an Endpoint
// MatchFunc allows custom logic for mapping packets to an Endpoint.
type MatchFunc func([]byte) bool
// MatchAll always returns true
// MatchAll always returns true.
func MatchAll([]byte) bool {
return true
}
// MatchRange returns true if the first byte of buf is in [lower..upper]
// MatchRange returns true if the first byte of buf is in [lower..upper].
func MatchRange(lower, upper byte, buf []byte) bool {
if len(buf) < 1 {
return false
}
b := buf[0]
return b >= lower && b <= upper
}
@@ -35,13 +36,13 @@ func MatchRange(lower, upper byte, buf []byte) bool {
// +----------------+
// MatchDTLS is a MatchFunc that accepts packets with the first byte in [20..63]
// as defied in RFC7983
// as defied in RFC7983.
func MatchDTLS(b []byte) bool {
return MatchRange(20, 63, b)
}
// MatchSRTPOrSRTCP is a MatchFunc that accepts packets with the first byte in [128..191]
// as defied in RFC7983
// as defied in RFC7983.
func MatchSRTPOrSRTCP(b []byte) bool {
return MatchRange(128, 191, b)
}
@@ -51,15 +52,16 @@ func isRTCP(buf []byte) bool {
if len(buf) < 4 {
return false
}
return buf[1] >= 192 && buf[1] <= 223
}
// MatchSRTP is a MatchFunc that only matches SRTP and not SRTCP
// MatchSRTP is a MatchFunc that only matches SRTP and not SRTCP.
func MatchSRTP(buf []byte) bool {
return MatchSRTPOrSRTCP(buf) && !isRTCP(buf)
}
// MatchSRTCP is a MatchFunc that only matches SRTCP and not SRTP
// MatchSRTCP is a MatchFunc that only matches SRTCP and not SRTP.
func MatchSRTCP(buf []byte) bool {
return MatchSRTPOrSRTCP(buf) && isRTCP(buf)
}

View File

@@ -28,7 +28,7 @@ func RandUint32() uint32 {
return globalMathRandomGenerator.Uint32()
}
// FlattenErrs flattens multiple errors into one
// FlattenErrs flattens multiple errors into one.
func FlattenErrs(errs []error) error {
errs2 := []error{}
for _, e := range errs {
@@ -39,6 +39,7 @@ func FlattenErrs(errs []error) error {
if len(errs2) == 0 {
return nil
}
return multiError(errs2)
}
@@ -71,5 +72,6 @@ func (me multiError) Is(err error) bool {
}
}
}
return false
}

View File

@@ -117,8 +117,12 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", videoRTCPFeedback},
PayloadType: 102,
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
videoRTCPFeedback,
},
PayloadType: 102,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=102", nil},
@@ -126,8 +130,12 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", videoRTCPFeedback},
PayloadType: 104,
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f",
videoRTCPFeedback,
},
PayloadType: 104,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=104", nil},
@@ -135,8 +143,12 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", videoRTCPFeedback},
PayloadType: 106,
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f",
videoRTCPFeedback,
},
PayloadType: 106,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=106", nil},
@@ -144,8 +156,12 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f", videoRTCPFeedback},
PayloadType: 108,
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f",
videoRTCPFeedback,
},
PayloadType: 108,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=108", nil},
@@ -153,8 +169,12 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f", videoRTCPFeedback},
PayloadType: 127,
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f",
videoRTCPFeedback,
},
PayloadType: 127,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=127", nil},
@@ -162,8 +182,13 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f", videoRTCPFeedback},
PayloadType: 39,
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264,
90000, 0,
"level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f",
videoRTCPFeedback,
},
PayloadType: 39,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=39", nil},
@@ -198,8 +223,12 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f", videoRTCPFeedback},
PayloadType: 112,
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0,
"level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f",
videoRTCPFeedback,
},
PayloadType: 112,
},
{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=112", nil},
@@ -214,13 +243,14 @@ func (m *MediaEngine) RegisterDefaultCodecs() error {
return nil
}
// addCodec will append codec if it not exists
// addCodec will append codec if it not exists.
func (m *MediaEngine) addCodec(codecs []RTPCodecParameters, codec RTPCodecParameters) []RTPCodecParameters {
for _, c := range codecs {
if c.MimeType == codec.MimeType && c.PayloadType == codec.PayloadType {
return codecs
}
}
return append(codecs, codec)
}
@@ -240,12 +270,19 @@ func (m *MediaEngine) RegisterCodec(codec RTPCodecParameters, typ RTPCodecType)
default:
return ErrUnknownType
}
return nil
}
// RegisterHeaderExtension adds a header extension to the MediaEngine
// To determine the negotiated value use `GetHeaderExtensionID` after signaling is complete
func (m *MediaEngine) RegisterHeaderExtension(extension RTPHeaderExtensionCapability, typ RTPCodecType, allowedDirections ...RTPTransceiverDirection) error {
// To determine the negotiated value use `GetHeaderExtensionID` after signaling is complete.
//
//nolint:cyclop
func (m *MediaEngine) RegisterHeaderExtension(
extension RTPHeaderExtensionCapability,
typ RTPCodecType,
allowedDirections ...RTPTransceiverDirection,
) error {
m.mu.Lock()
defer m.mu.Unlock()
@@ -306,8 +343,11 @@ func (m *MediaEngine) RegisterFeedback(feedback RTCPFeedback, typ RTPCodecType)
}
// getHeaderExtensionID returns the negotiated ID for a header extension.
// If the Header Extension isn't enabled ok will be false
func (m *MediaEngine) getHeaderExtensionID(extension RTPHeaderExtensionCapability) (val int, audioNegotiated, videoNegotiated bool) {
// If the Header Extension isn't enabled ok will be false.
func (m *MediaEngine) getHeaderExtensionID(extension RTPHeaderExtensionCapability) (
val int,
audioNegotiated, videoNegotiated bool,
) {
m.mu.RLock()
defer m.mu.RUnlock()
@@ -325,7 +365,7 @@ func (m *MediaEngine) getHeaderExtensionID(extension RTPHeaderExtensionCapabilit
}
// copy copies any user modifiable state of the MediaEngine
// all internal state is reset
// all internal state is reset.
func (m *MediaEngine) copy() *MediaEngine {
m.mu.Lock()
defer m.mu.Unlock()
@@ -337,6 +377,7 @@ func (m *MediaEngine) copy() *MediaEngine {
if len(m.headerExtensions) > 0 {
cloned.negotiatedHeaderExtensions = map[int]mediaEngineHeaderExtension{}
}
return cloned
}
@@ -346,6 +387,7 @@ func findCodecByPayload(codecs []RTPCodecParameters, payloadType PayloadType) *R
return &codec
}
}
return nil
}
@@ -393,7 +435,7 @@ func (m *MediaEngine) collectStats(collector *statsReportCollector) {
PayloadType: codec.PayloadType,
MimeType: codec.MimeType,
ClockRate: codec.ClockRate,
Channels: uint8(codec.Channels),
Channels: uint8(codec.Channels), //nolint:gosec // G115
SDPFmtpLine: codec.SDPFmtpLine,
}
@@ -405,15 +447,21 @@ func (m *MediaEngine) collectStats(collector *statsReportCollector) {
statsLoop(m.audioCodecs)
}
// Look up a codec and enable if it exists
func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCodecType, exactMatches, partialMatches []RTPCodecParameters) (RTPCodecParameters, codecMatchType, error) {
// Look up a codec and enable if it exists.
//
//nolint:cyclop
func (m *MediaEngine) matchRemoteCodec(
remoteCodec RTPCodecParameters,
typ RTPCodecType,
exactMatches, partialMatches []RTPCodecParameters,
) (RTPCodecParameters, codecMatchType, error) {
codecs := m.videoCodecs
if typ == RTPCodecTypeAudio {
codecs = m.audioCodecs
}
remoteFmtp := fmtp.Parse(remoteCodec.RTPCodecCapability.MimeType, remoteCodec.RTPCodecCapability.SDPFmtpLine)
if apt, hasApt := remoteFmtp.Parameter("apt"); hasApt {
if apt, hasApt := remoteFmtp.Parameter("apt"); hasApt { //nolint:nestif
payloadType, err := strconv.ParseUint(apt, 10, 8)
if err != nil {
return RTPCodecParameters{}, codecMatchNone, err
@@ -425,6 +473,7 @@ func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCo
if codec.PayloadType == PayloadType(payloadType) {
aptMatch = codecMatchExact
aptCodec = codec
break
}
}
@@ -434,6 +483,7 @@ func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCo
if codec.PayloadType == PayloadType(payloadType) {
aptMatch = codecMatchPartial
aptCodec = codec
break
}
}
@@ -446,22 +496,29 @@ func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCo
// replace the apt value with the original codec's payload type
toMatchCodec := remoteCodec
if aptMatched, mt := codecParametersFuzzySearch(aptCodec, codecs); mt == aptMatch {
toMatchCodec.SDPFmtpLine = strings.Replace(toMatchCodec.SDPFmtpLine, fmt.Sprintf("apt=%d", payloadType), fmt.Sprintf("apt=%d", aptMatched.PayloadType), 1)
toMatchCodec.SDPFmtpLine = strings.Replace(
toMatchCodec.SDPFmtpLine,
fmt.Sprintf("apt=%d", payloadType),
fmt.Sprintf("apt=%d", aptMatched.PayloadType),
1,
)
}
// if apt's media codec is partial match, then apt codec must be partial match too
// if apt's media codec is partial match, then apt codec must be partial match too.
localCodec, matchType := codecParametersFuzzySearch(toMatchCodec, codecs)
if matchType == codecMatchExact && aptMatch == codecMatchPartial {
matchType = codecMatchPartial
}
return localCodec, matchType, nil
}
localCodec, matchType := codecParametersFuzzySearch(remoteCodec, codecs)
return localCodec, matchType, nil
}
// Update header extensions from a remote media section
// Update header extensions from a remote media section.
func (m *MediaEngine) updateHeaderExtensionFromMediaSection(media *sdp.MediaDescription) error {
var typ RTPCodecType
switch {
@@ -482,10 +539,11 @@ func (m *MediaEngine) updateHeaderExtensionFromMediaSection(media *sdp.MediaDesc
return err
}
}
return nil
}
// Look up a header extension and enable if it exists
// Look up a header extension and enable if it exists.
func (m *MediaEngine) updateHeaderExtension(id int, extension string, typ RTPCodecType) error {
if m.negotiatedHeaderExtensions == nil {
return nil
@@ -508,6 +566,7 @@ func (m *MediaEngine) updateHeaderExtension(id int, extension string, typ RTPCod
m.negotiatedHeaderExtensions[id] = h
}
}
return nil
}
@@ -521,8 +580,8 @@ func (m *MediaEngine) pushCodecs(codecs []RTPCodecParameters, typ RTPCodecType)
}
}
// Update the MediaEngine from a remote description
func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) error {
// Update the MediaEngine from a remote description.
func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) error { //nolint:cyclop
m.mu.Lock()
defer m.mu.Unlock()
@@ -549,6 +608,7 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e
if err := m.updateHeaderExtensionFromMediaSection(media); err != nil {
return err
}
continue
}
@@ -590,6 +650,7 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e
return err
}
}
return nil
}
@@ -614,7 +675,8 @@ func (m *MediaEngine) getCodecsByKind(typ RTPCodecType) []RTPCodecParameters {
return nil
}
func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPTransceiverDirection) RTPParameters { //nolint:gocognit
//nolint:gocognit,cyclop
func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPTransceiverDirection) RTPParameters {
headerExtensions := make([]RTPHeaderExtensionParameter, 0)
// perform before locking to prevent recursive RLocks
@@ -622,21 +684,24 @@ func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPT
m.mu.RLock()
defer m.mu.RUnlock()
if m.negotiatedVideo && typ == RTPCodecTypeVideo ||
m.negotiatedAudio && typ == RTPCodecTypeAudio {
//nolint:nestif
if (m.negotiatedVideo && typ == RTPCodecTypeVideo) || (m.negotiatedAudio && typ == RTPCodecTypeAudio) {
for id, e := range m.negotiatedHeaderExtensions {
if haveRTPTransceiverDirectionIntersection(e.allowedDirections, directions) && (e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo) {
if haveRTPTransceiverDirectionIntersection(e.allowedDirections, directions) &&
(e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo) {
headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
}
}
} else {
mediaHeaderExtensions := make(map[int]mediaEngineHeaderExtension)
for _, e := range m.headerExtensions {
for _, ext := range m.headerExtensions {
usingNegotiatedID := false
for id := range m.negotiatedHeaderExtensions {
if m.negotiatedHeaderExtensions[id].uri == e.uri {
if m.negotiatedHeaderExtensions[id].uri == ext.uri {
usingNegotiatedID = true
mediaHeaderExtensions[id] = e
mediaHeaderExtensions[id] = ext
break
}
}
@@ -647,7 +712,8 @@ func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPT
idAvailable = false
}
if _, taken := m.negotiatedHeaderExtensions[id]; idAvailable && !taken {
mediaHeaderExtensions[id] = e
mediaHeaderExtensions[id] = ext
break
}
}
@@ -655,7 +721,8 @@ func (m *MediaEngine) getRTPParametersByKind(typ RTPCodecType, directions []RTPT
}
for id, e := range mediaHeaderExtensions {
if haveRTPTransceiverDirectionIntersection(e.allowedDirections, directions) && (e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo) {
if haveRTPTransceiverDirectionIntersection(e.allowedDirections, directions) &&
(e.isAudio && typ == RTPCodecTypeAudio || e.isVideo && typ == RTPCodecTypeVideo) {
headerExtensions = append(headerExtensions, RTPHeaderExtensionParameter{ID: id, URI: e.uri})
}
}

View File

@@ -18,6 +18,7 @@ import (
)
// pion/webrtc#1078
// .
func TestOpusCase(t *testing.T) {
pc, err := NewPeerConnection(Configuration{})
assert.NoError(t, err)
@@ -33,6 +34,7 @@ func TestOpusCase(t *testing.T) {
}
// pion/example-webrtc-applications#89
// .
func TestVideoCase(t *testing.T) {
pc, err := NewPeerConnection(Configuration{})
assert.NoError(t, err)
@@ -49,10 +51,11 @@ func TestVideoCase(t *testing.T) {
assert.NoError(t, pc.Close())
}
func TestMediaEngineRemoteDescription(t *testing.T) {
func TestMediaEngineRemoteDescription(t *testing.T) { //nolint:maintidx
mustParse := func(raw string) sdp.SessionDescription {
s := sdp.SessionDescription{}
assert.NoError(t, s.Unmarshal([]byte(raw)))
return s
}
@@ -62,12 +65,12 @@ o=- 4596489990601351948 2 IN IP4 127.0.0.1
s=-
t=0 0
`
m := MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
assert.NoError(t, m.updateFromRemoteDescription(mustParse(noMedia)))
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(noMedia)))
assert.False(t, m.negotiatedVideo)
assert.False(t, m.negotiatedAudio)
assert.False(t, mediaEngine.negotiatedVideo)
assert.False(t, mediaEngine.negotiatedAudio)
})
t.Run("Enable Opus", func(t *testing.T) {
@@ -80,14 +83,14 @@ a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10; useinbandfec=1
`
m := MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusSamePayload)))
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(opusSamePayload)))
assert.False(t, m.negotiatedVideo)
assert.True(t, m.negotiatedAudio)
assert.False(t, mediaEngine.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedAudio)
opusCodec, _, err := m.getCodecByPayload(111)
opusCodec, _, err := mediaEngine.getCodecByPayload(111)
assert.NoError(t, err)
assert.Equal(t, opusCodec.MimeType, MimeTypeOpus)
})
@@ -102,17 +105,17 @@ a=rtpmap:112 opus/48000/2
a=fmtp:112 minptime=10; useinbandfec=1
`
m := MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusSamePayload)))
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(opusSamePayload)))
assert.False(t, m.negotiatedVideo)
assert.True(t, m.negotiatedAudio)
assert.False(t, mediaEngine.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedAudio)
_, _, err := m.getCodecByPayload(111)
_, _, err := mediaEngine.getCodecByPayload(111)
assert.Error(t, err)
opusCodec, _, err := m.getCodecByPayload(112)
opusCodec, _, err := mediaEngine.getCodecByPayload(112)
assert.NoError(t, err)
assert.Equal(t, opusCodec.MimeType, MimeTypeOpus)
})
@@ -127,14 +130,14 @@ a=rtpmap:96 opus/48000/2
a=fmtp:96 minptime=10; useinbandfec=1
`
m := MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusSamePayload)))
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(opusSamePayload)))
assert.False(t, m.negotiatedVideo)
assert.True(t, m.negotiatedAudio)
assert.False(t, mediaEngine.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedAudio)
opusCodec, _, err := m.getCodecByPayload(96)
opusCodec, _, err := mediaEngine.getCodecByPayload(96)
assert.NoError(t, err)
assert.Equal(t, opusCodec.MimeType, MimeTypeOpus)
})
@@ -149,14 +152,14 @@ a=rtpmap:111 OPUS/48000/2
a=fmtp:111 minptime=10; useinbandfec=1
`
m := MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusUpcase)))
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(opusUpcase)))
assert.False(t, m.negotiatedVideo)
assert.True(t, m.negotiatedAudio)
assert.False(t, mediaEngine.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedAudio)
opusCodec, _, err := m.getCodecByPayload(111)
opusCodec, _, err := mediaEngine.getCodecByPayload(111)
assert.NoError(t, err)
assert.Equal(t, opusCodec.MimeType, "audio/OPUS")
})
@@ -170,14 +173,14 @@ m=audio 9 UDP/TLS/RTP/SAVPF 111
a=rtpmap:111 opus/48000/2
`
m := MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
assert.NoError(t, m.updateFromRemoteDescription(mustParse(opusNoFmtp)))
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(opusNoFmtp)))
assert.False(t, m.negotiatedVideo)
assert.True(t, m.negotiatedAudio)
assert.False(t, mediaEngine.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedAudio)
opusCodec, _, err := m.getCodecByPayload(111)
opusCodec, _, err := mediaEngine.getCodecByPayload(111)
assert.NoError(t, err)
assert.Equal(t, opusCodec.MimeType, MimeTypeOpus)
})
@@ -193,20 +196,26 @@ a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=rtpmap:111 opus/48000/2
`
m := MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: sdp.SDESMidURI}, RTPCodecTypeAudio))
assert.NoError(t, m.updateFromRemoteDescription(mustParse(headerExtensions)))
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{URI: sdp.SDESMidURI}, RTPCodecTypeAudio),
)
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(headerExtensions)))
assert.False(t, m.negotiatedVideo)
assert.True(t, m.negotiatedAudio)
assert.False(t, mediaEngine.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedAudio)
absID, absAudioEnabled, absVideoEnabled := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.ABSSendTimeURI})
absID, absAudioEnabled, absVideoEnabled := mediaEngine.getHeaderExtensionID(
RTPHeaderExtensionCapability{sdp.ABSSendTimeURI},
)
assert.Equal(t, absID, 0)
assert.False(t, absAudioEnabled)
assert.False(t, absVideoEnabled)
midID, midAudioEnabled, midVideoEnabled := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESMidURI})
midID, midAudioEnabled, midVideoEnabled := mediaEngine.getHeaderExtensionID(
RTPHeaderExtensionCapability{sdp.SDESMidURI},
)
assert.Equal(t, midID, 7)
assert.True(t, midAudioEnabled)
assert.False(t, midVideoEnabled)
@@ -225,21 +234,29 @@ a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=rtpmap:111 opus/48000/2
`
m := MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: "urn:ietf:params:rtp-hdrext:sdes:mid"}, RTPCodecTypeAudio))
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{URI: "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"}, RTPCodecTypeAudio))
assert.NoError(t, m.updateFromRemoteDescription(mustParse(headerExtensions)))
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{URI: "urn:ietf:params:rtp-hdrext:sdes:mid"}, RTPCodecTypeAudio,
))
assert.NoError(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{URI: "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id"}, RTPCodecTypeAudio,
))
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(headerExtensions)))
assert.False(t, m.negotiatedVideo)
assert.True(t, m.negotiatedAudio)
assert.False(t, mediaEngine.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedAudio)
absID, absAudioEnabled, absVideoEnabled := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.ABSSendTimeURI})
absID, absAudioEnabled, absVideoEnabled := mediaEngine.getHeaderExtensionID(
RTPHeaderExtensionCapability{sdp.ABSSendTimeURI},
)
assert.Equal(t, absID, 0)
assert.False(t, absAudioEnabled)
assert.False(t, absVideoEnabled)
midID, midAudioEnabled, midVideoEnabled := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESMidURI})
midID, midAudioEnabled, midVideoEnabled := mediaEngine.getHeaderExtensionID(
RTPHeaderExtensionCapability{sdp.SDESMidURI},
)
assert.Equal(t, midID, 7)
assert.True(t, midAudioEnabled)
assert.False(t, midVideoEnabled)
@@ -256,21 +273,23 @@ a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f
a=rtpmap:98 H264/90000
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
`
m := MediaEngine{}
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", nil},
PayloadType: 127,
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", nil,
},
PayloadType: 127,
}, RTPCodecTypeVideo))
assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(profileLevels)))
assert.True(t, m.negotiatedVideo)
assert.False(t, m.negotiatedAudio)
assert.True(t, mediaEngine.negotiatedVideo)
assert.False(t, mediaEngine.negotiatedAudio)
supportedH264, _, err := m.getCodecByPayload(98)
supportedH264, _, err := mediaEngine.getCodecByPayload(98)
assert.NoError(t, err)
assert.Equal(t, supportedH264.MimeType, MimeTypeH264)
_, _, err = m.getCodecByPayload(96)
_, _, err = mediaEngine.getCodecByPayload(96)
assert.Error(t, err)
})
@@ -283,14 +302,16 @@ m=video 60323 UDP/TLS/RTP/SAVPF 96 98
a=rtpmap:96 H264/90000
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=640c1f
`
m := MediaEngine{}
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", nil},
PayloadType: 127,
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", nil,
},
PayloadType: 127,
}, RTPCodecTypeVideo))
assert.Error(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
assert.Error(t, mediaEngine.updateFromRemoteDescription(mustParse(profileLevels)))
_, _, err := m.getCodecByPayload(96)
_, _, err := mediaEngine.getCodecByPayload(96)
assert.Error(t, err)
})
@@ -302,16 +323,16 @@ t=0 0
m=video 60323 UDP/TLS/RTP/SAVPF 96
a=rtpmap:96 VP9/90000
`
m := MediaEngine{}
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=0", nil},
PayloadType: 98,
}, RTPCodecTypeVideo))
assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(profileLevels)))
assert.True(t, m.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedVideo)
_, _, err := m.getCodecByPayload(96)
_, _, err := mediaEngine.getCodecByPayload(96)
assert.NoError(t, err)
})
@@ -323,16 +344,16 @@ t=0 0
m=video 60323 UDP/TLS/RTP/SAVPF 96
a=rtpmap:96 VP8/90000
`
m := MediaEngine{}
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 96,
}, RTPCodecTypeVideo))
assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(profileLevels)))
assert.True(t, m.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedVideo)
_, _, err := m.getCodecByPayload(96)
_, _, err := mediaEngine.getCodecByPayload(96)
assert.NoError(t, err)
})
@@ -358,64 +379,68 @@ a=fmtp:96 profile-id=2
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
`
m := MediaEngine{}
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 96,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=96", nil},
PayloadType: 97,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", nil},
PayloadType: 102,
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", nil,
},
PayloadType: 102,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=102", nil},
PayloadType: 103,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", nil},
PayloadType: 104,
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f", nil,
},
PayloadType: 104,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=104", nil},
PayloadType: 105,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=2", nil},
PayloadType: 98,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=98", nil},
PayloadType: 99,
}, RTPCodecTypeVideo))
assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(profileLevels)))
assert.True(t, m.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedVideo)
vp9Codec, _, err := m.getCodecByPayload(96)
vp9Codec, _, err := mediaEngine.getCodecByPayload(96)
assert.NoError(t, err)
assert.Equal(t, vp9Codec.MimeType, MimeTypeVP9)
vp9RTX, _, err := m.getCodecByPayload(97)
vp9RTX, _, err := mediaEngine.getCodecByPayload(97)
assert.NoError(t, err)
assert.Equal(t, vp9RTX.MimeType, MimeTypeRTX)
h264P1Codec, _, err := m.getCodecByPayload(106)
h264P1Codec, _, err := mediaEngine.getCodecByPayload(106)
assert.NoError(t, err)
assert.Equal(t, h264P1Codec.MimeType, MimeTypeH264)
assert.Equal(t, h264P1Codec.SDPFmtpLine, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f")
h264P1RTX, _, err := m.getCodecByPayload(107)
h264P1RTX, _, err := mediaEngine.getCodecByPayload(107)
assert.NoError(t, err)
assert.Equal(t, h264P1RTX.MimeType, MimeTypeRTX)
assert.Equal(t, h264P1RTX.SDPFmtpLine, "apt=106")
h264P0Codec, _, err := m.getCodecByPayload(108)
h264P0Codec, _, err := mediaEngine.getCodecByPayload(108)
assert.NoError(t, err)
assert.Equal(t, h264P0Codec.MimeType, MimeTypeH264)
assert.Equal(t, h264P0Codec.SDPFmtpLine, "level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f")
h264P0RTX, _, err := m.getCodecByPayload(109)
h264P0RTX, _, err := mediaEngine.getCodecByPayload(109)
assert.NoError(t, err)
assert.Equal(t, h264P0RTX.MimeType, MimeTypeRTX)
assert.Equal(t, h264P0RTX.SDPFmtpLine, "apt=108")
@@ -433,24 +458,24 @@ a=fmtp:96 profile-id=2
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
`
m := MediaEngine{}
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 94,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP9, 90000, 0, "profile-id=1", nil},
PayloadType: 96,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeRTX, 90000, 0, "apt=96", nil},
PayloadType: 97,
}, RTPCodecTypeVideo))
assert.NoError(t, m.updateFromRemoteDescription(mustParse(profileLevels)))
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(profileLevels)))
assert.True(t, m.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedVideo)
_, _, err := m.getCodecByPayload(97)
_, _, err := mediaEngine.getCodecByPayload(97)
assert.ErrorIs(t, err, ErrCodecNotFound)
})
}
@@ -468,52 +493,79 @@ func TestMediaEngineHeaderExtensionDirection(t *testing.T) {
}
t.Run("No Direction", func(t *testing.T) {
m := &MediaEngine{}
registerCodec(m)
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio))
mediaEngine := &MediaEngine{}
registerCodec(mediaEngine)
assert.NoError(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio,
))
params := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
params := mediaEngine.getRTPParametersByKind(
RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly},
)
assert.Equal(t, 1, len(params.HeaderExtensions))
})
t.Run("Same Direction", func(t *testing.T) {
m := &MediaEngine{}
registerCodec(m)
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionRecvonly))
mediaEngine := &MediaEngine{}
registerCodec(mediaEngine)
assert.NoError(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionRecvonly,
))
params := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
params := mediaEngine.getRTPParametersByKind(
RTPCodecTypeAudio,
[]RTPTransceiverDirection{RTPTransceiverDirectionRecvonly},
)
assert.Equal(t, 1, len(params.HeaderExtensions))
})
t.Run("Different Direction", func(t *testing.T) {
m := &MediaEngine{}
registerCodec(m)
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionSendonly))
mediaEngine := &MediaEngine{}
registerCodec(mediaEngine)
assert.NoError(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionSendonly,
))
params := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
params := mediaEngine.getRTPParametersByKind(
RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly},
)
assert.Equal(t, 0, len(params.HeaderExtensions))
})
t.Run("Invalid Direction", func(t *testing.T) {
m := &MediaEngine{}
registerCodec(m)
mediaEngine := &MediaEngine{}
registerCodec(mediaEngine)
assert.ErrorIs(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionSendrecv), ErrRegisterHeaderExtensionInvalidDirection)
assert.ErrorIs(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionInactive), ErrRegisterHeaderExtensionInvalidDirection)
assert.ErrorIs(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirection(0)), ErrRegisterHeaderExtensionInvalidDirection)
assert.ErrorIs(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionSendrecv,
), ErrRegisterHeaderExtensionInvalidDirection)
assert.ErrorIs(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirectionInactive,
), ErrRegisterHeaderExtensionInvalidDirection)
assert.ErrorIs(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio, RTPTransceiverDirection(0),
), ErrRegisterHeaderExtensionInvalidDirection)
})
t.Run("Unique extmapid with different codec", func(t *testing.T) {
m := &MediaEngine{}
registerCodec(m)
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio))
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"pion-header-test2"}, RTPCodecTypeVideo))
mediaEngine := &MediaEngine{}
registerCodec(mediaEngine)
assert.NoError(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{"pion-header-test"}, RTPCodecTypeAudio),
)
assert.NoError(t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{"pion-header-test2"}, RTPCodecTypeVideo),
)
audio := m.getRTPParametersByKind(RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
video := m.getRTPParametersByKind(RTPCodecTypeVideo, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly})
audio := mediaEngine.getRTPParametersByKind(
RTPCodecTypeAudio, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly},
)
video := mediaEngine.getRTPParametersByKind(
RTPCodecTypeVideo, []RTPTransceiverDirection{RTPTransceiverDirectionRecvonly},
)
assert.Equal(t, 1, len(audio.HeaderExtensions))
assert.Equal(t, 1, len(video.HeaderExtensions))
@@ -521,23 +573,23 @@ func TestMediaEngineHeaderExtensionDirection(t *testing.T) {
})
}
// If a user attempts to register a codec twice we should just discard duplicate calls
// If a user attempts to register a codec twice we should just discard duplicate calls.
func TestMediaEngineDoubleRegister(t *testing.T) {
m := MediaEngine{}
mediaEngine := MediaEngine{}
assert.NoError(t, m.RegisterCodec(
assert.NoError(t, mediaEngine.RegisterCodec(
RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 0, "", nil},
PayloadType: 111,
}, RTPCodecTypeAudio))
assert.NoError(t, m.RegisterCodec(
assert.NoError(t, mediaEngine.RegisterCodec(
RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeOpus, 48000, 0, "", nil},
PayloadType: 111,
}, RTPCodecTypeAudio))
assert.Equal(t, len(m.audioCodecs), 1)
assert.Equal(t, len(mediaEngine.audioCodecs), 1)
}
// The cloned MediaEngine instance should be able to update negotiated header extensions.
@@ -569,6 +621,7 @@ func TestExtensionIdCollision(t *testing.T) {
mustParse := func(raw string) sdp.SessionDescription {
s := sdp.SessionDescription{}
assert.NoError(t, s.Unmarshal([]byte(raw)))
return s
}
sdpSnippet := `v=0
@@ -582,36 +635,57 @@ a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=rtpmap:111 opus/48000/2
`
m := MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.SDESMidURI}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{"urn:3gpp:video-orientation"}, RTPCodecTypeVideo))
assert.NoError(
t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{sdp.SDESMidURI}, RTPCodecTypeVideo,
),
)
assert.NoError(
t, mediaEngine.RegisterHeaderExtension(
RTPHeaderExtensionCapability{"urn:3gpp:video-orientation"}, RTPCodecTypeVideo,
),
)
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.SDESMidURI}, RTPCodecTypeAudio))
assert.NoError(t, m.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.AudioLevelURI}, RTPCodecTypeAudio))
assert.NoError(
t, mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.SDESMidURI}, RTPCodecTypeAudio),
)
assert.NoError(
t, mediaEngine.RegisterHeaderExtension(RTPHeaderExtensionCapability{sdp.AudioLevelURI}, RTPCodecTypeAudio),
)
assert.NoError(t, m.updateFromRemoteDescription(mustParse(sdpSnippet)))
assert.NoError(t, mediaEngine.updateFromRemoteDescription(mustParse(sdpSnippet)))
assert.True(t, m.negotiatedAudio)
assert.False(t, m.negotiatedVideo)
assert.True(t, mediaEngine.negotiatedAudio)
assert.False(t, mediaEngine.negotiatedVideo)
id, audioNegotiated, videoNegotiated := m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.ABSSendTimeURI})
id, audioNegotiated, videoNegotiated := mediaEngine.getHeaderExtensionID(RTPHeaderExtensionCapability{
sdp.ABSSendTimeURI,
})
assert.Equal(t, id, 0)
assert.False(t, audioNegotiated)
assert.False(t, videoNegotiated)
id, audioNegotiated, videoNegotiated = m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.SDESMidURI})
id, audioNegotiated, videoNegotiated = mediaEngine.getHeaderExtensionID(RTPHeaderExtensionCapability{
sdp.SDESMidURI,
})
assert.Equal(t, id, 2)
assert.True(t, audioNegotiated)
assert.False(t, videoNegotiated)
id, audioNegotiated, videoNegotiated = m.getHeaderExtensionID(RTPHeaderExtensionCapability{sdp.AudioLevelURI})
id, audioNegotiated, videoNegotiated = mediaEngine.getHeaderExtensionID(RTPHeaderExtensionCapability{
sdp.AudioLevelURI,
})
assert.Equal(t, id, 1)
assert.True(t, audioNegotiated)
assert.False(t, videoNegotiated)
params := m.getRTPParametersByKind(RTPCodecTypeVideo, []RTPTransceiverDirection{RTPTransceiverDirectionSendonly})
params := mediaEngine.getRTPParametersByKind(
RTPCodecTypeVideo,
[]RTPTransceiverDirection{RTPTransceiverDirectionSendonly},
)
extensions := params.HeaderExtensions
assert.Equal(t, 2, len(extensions))
@@ -689,12 +763,19 @@ a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001
for _, codec := range []RTPCodecParameters{
{
RTPCodecCapability: RTPCodecCapability{MimeType: mimeTypeVp8, ClockRate: 90000, RTCPFeedback: feedback},
PayloadType: 96,
RTPCodecCapability: RTPCodecCapability{
MimeType: mimeTypeVp8, ClockRate: 90000, RTCPFeedback: feedback,
},
PayloadType: 96,
},
{
RTPCodecCapability: RTPCodecCapability{MimeType: "video/h264", ClockRate: 90000, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f", RTCPFeedback: feedback},
PayloadType: 127,
RTPCodecCapability: RTPCodecCapability{
MimeType: "video/h264",
ClockRate: 90000,
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
RTCPFeedback: feedback,
},
PayloadType: 127,
},
} {
assert.NoError(t, me.RegisterCodec(codec, RTPCodecTypeVideo))
@@ -723,7 +804,7 @@ a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001
}
}
// rtcp-fb should be an intersection of local and remote
// rtcp-fb should be an intersection of local and remote.
func TestRTCPFeedbackHandling(t *testing.T) {
const offerSdp = `
v=0
@@ -748,9 +829,11 @@ a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 nack
`
runTest := func(createTransceiver bool, t *testing.T) {
m := &MediaEngine{}
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
runTest := func(t *testing.T, createTransceiver bool) {
t.Helper()
mediaEngine := &MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeVP8, ClockRate: 90000, RTCPFeedback: []RTCPFeedback{
{Type: TypeRTCPFBTransportCC},
{Type: TypeRTCPFBNACK},
@@ -758,7 +841,7 @@ a=rtcp-fb:96 nack
PayloadType: 96,
}, RTPCodecTypeVideo))
peerConnection, err := NewAPI(WithMediaEngine(m)).NewPeerConnection(Configuration{})
peerConnection, err := NewAPI(WithMediaEngine(mediaEngine)).NewPeerConnection(Configuration{})
assert.NoError(t, err)
if createTransceiver {
@@ -786,10 +869,10 @@ a=rtcp-fb:96 nack
}
t.Run("recvonly", func(t *testing.T) {
runTest(false, t)
runTest(t, false)
})
t.Run("sendrecv", func(t *testing.T) {
runTest(true, t)
runTest(t, true)
})
}

View File

@@ -18,11 +18,11 @@ func supportedNetworkTypes() []NetworkType {
}
}
// NetworkType represents the type of network
// NetworkType represents the type of network.
type NetworkType int
const (
// NetworkTypeUnknown is the enum's zero-value
// NetworkTypeUnknown is the enum's zero-value.
NetworkTypeUnknown NetworkType = iota
// NetworkTypeUDP4 indicates UDP over IPv4.
@@ -61,7 +61,7 @@ func (t NetworkType) String() string {
}
}
// Protocol returns udp or tcp
// Protocol returns udp or tcp.
func (t NetworkType) Protocol() string {
switch t {
case NetworkTypeUDP4:

View File

@@ -18,7 +18,7 @@ type AnswerOptions struct {
}
// OfferOptions structure describes the options used to control the offer
// creation process
// creation process.
type OfferOptions struct {
OfferAnswerOptions

View File

@@ -8,7 +8,7 @@ import (
"sync"
)
// Operation is a function
// Operation is a function.
type operation func()
// Operations is a task executor.
@@ -64,10 +64,11 @@ func (o *operations) tryEnqueue(op operation) bool {
return true
}
// IsEmpty checks if there are tasks in the queue
// IsEmpty checks if there are tasks in the queue.
func (o *operations) IsEmpty() bool {
o.mu.Lock()
defer o.mu.Unlock()
return o.ops.Len() == 0
}
@@ -93,6 +94,7 @@ func (o *operations) GracefulClose() {
o.mu.Lock()
if o.isClosed {
o.mu.Unlock()
return
}
// do not enqueue anymore ops from here on
@@ -120,6 +122,7 @@ func (o *operations) pop() func() {
if op, ok := e.Value.(operation); ok {
return op
}
return nil
}
@@ -132,6 +135,7 @@ func (o *operations) start() {
if o.ops.Len() == 0 || o.isClosed {
o.busyCh = nil
return
}

File diff suppressed because it is too large Load Diff

View File

@@ -63,7 +63,7 @@ func TestPeerConnection_Close(t *testing.T) {
<-awaitICEClosed
}
// Assert that a PeerConnection that is shutdown before ICE starts doesn't leak
// Assert that a PeerConnection that is shutdown before ICE starts doesn't leak.
func TestPeerConnection_Close_PreICE(t *testing.T) {
// Limit runtime in case of deadlocks
lim := test.TimeOut(time.Second * 30)
@@ -111,7 +111,7 @@ func TestPeerConnection_Close_PreICE(t *testing.T) {
}
}
func TestPeerConnection_Close_DuringICE(t *testing.T) {
func TestPeerConnection_Close_DuringICE(t *testing.T) { //nolint:cyclop
// Limit runtime in case of deadlocks
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()

View File

@@ -340,7 +340,7 @@ func TestPeerConnection_EventHandlers_Go(t *testing.T) {
}
// This test asserts that nothing deadlocks we try to shutdown when DTLS is in flight
// We ensure that DTLS is in flight by removing the mux func for it, so all inbound DTLS is lost
// We ensure that DTLS is in flight by removing the mux func for it, so all inbound DTLS is lost.
func TestPeerConnection_ShutdownNoDTLS(t *testing.T) {
lim := test.TimeOut(time.Second * 10)
defer lim.Stop()
@@ -477,7 +477,12 @@ func TestPeerConnection_satisfyTypeAndDirection(t *testing.T) {
{
"No local Transceivers, every remote should get nil",
[]RTPCodecType{RTPCodecTypeVideo, RTPCodecTypeAudio, RTPCodecTypeVideo, RTPCodecTypeVideo},
[]RTPTransceiverDirection{RTPTransceiverDirectionSendrecv, RTPTransceiverDirectionRecvonly, RTPTransceiverDirectionSendonly, RTPTransceiverDirectionInactive},
[]RTPTransceiverDirection{
RTPTransceiverDirectionSendrecv,
RTPTransceiverDirectionRecvonly,
RTPTransceiverDirectionSendonly,
RTPTransceiverDirectionInactive,
},
[]*RTPTransceiver{},
@@ -658,15 +663,19 @@ func TestOnICEGatheringStateChange(t *testing.T) {
assert.NoError(t, peerConn.Close())
}
// Assert Trickle ICE behaviors
func TestPeerConnectionTrickle(t *testing.T) {
// Assert Trickle ICE behaviors.
func TestPeerConnectionTrickle(t *testing.T) { //nolint:cyclop
offerPC, answerPC, err := newPair()
assert.NoError(t, err)
_, err = offerPC.CreateDataChannel("test-channel", nil)
assert.NoError(t, err)
addOrCacheCandidate := func(pc *PeerConnection, c *ICECandidate, candidateCache []ICECandidateInit) []ICECandidateInit {
addOrCacheCandidate := func(
pc *PeerConnection,
c *ICECandidate,
candidateCache []ICECandidateInit,
) []ICECandidateInit {
if c == nil {
return candidateCache
}
@@ -676,6 +685,7 @@ func TestPeerConnectionTrickle(t *testing.T) {
}
assert.NoError(t, pc.AddICECandidate(c.ToJSON()))
return candidateCache
}
@@ -752,7 +762,7 @@ func TestPeerConnectionTrickle(t *testing.T) {
closePairNow(t, offerPC, answerPC)
}
// Issue #1121, assert populateLocalCandidates doesn't mutate
// Issue #1121, assert populateLocalCandidates doesn't mutate.
func TestPopulateLocalCandidates(t *testing.T) {
t.Run("PendingLocalDescription shouldn't add extra mutations", func(t *testing.T) {
pc, err := NewPeerConnection(Configuration{})
@@ -792,7 +802,7 @@ func TestPopulateLocalCandidates(t *testing.T) {
})
}
// Assert that two agents that only generate mDNS candidates can connect
// Assert that two agents that only generate mDNS candidates can connect.
func TestMulticastDNSCandidates(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -896,7 +906,7 @@ func TestICERestart(t *testing.T) {
closePairNow(t, offerPC, answerPC)
}
// Assert error handling when an Agent is restart
// Assert error handling when an Agent is restart.
func TestICERestart_Error_Handling(t *testing.T) {
iceStates := make(chan ICEConnectionState, 100)
blockUntilICEState := func(wantedState ICEConnectionState) {
@@ -957,9 +967,9 @@ func TestICERestart_Error_Handling(t *testing.T) {
})
dataChannelAnswerer := make(chan *DataChannel)
offerPeerConnection.OnDataChannel(func(d *DataChannel) {
d.OnOpen(func() {
dataChannelAnswerer <- d
offerPeerConnection.OnDataChannel(func(dataChannel *DataChannel) {
dataChannel.OnOpen(func() {
dataChannelAnswerer <- dataChannel
})
})
@@ -1010,6 +1020,7 @@ func (r *trackRecords) newTrack() (*TrackLocalStaticRTP, error) {
trackID := fmt.Sprintf("pion-track-%d", len(r.trackIDs))
track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, trackID, "pion")
r.trackIDs[trackID] = struct{}{}
return track, err
}
@@ -1024,12 +1035,14 @@ func (r *trackRecords) handleTrack(t *TrackRemote, _ *RTPReceiver) {
func (r *trackRecords) remains() int {
r.mu.Lock()
defer r.mu.Unlock()
return len(r.trackIDs) - len(r.receivedTrackIDs)
}
// This test assure that all track events emits.
func TestPeerConnection_MassiveTracks(t *testing.T) {
func TestPeerConnection_MassiveTracks(t *testing.T) { //nolint:cyclop
var (
tRecs = &trackRecords{
trackIDs: make(map[string]struct{}),
@@ -1166,7 +1179,7 @@ a=mid:data
`
// this test asserts that if an ice-lite offer is received,
// pion will take the ICE-CONTROLLING role
// pion will take the ICE-CONTROLLING role.
func TestICELite(t *testing.T) {
peerConnection, err := NewPeerConnection(Configuration{})
assert.NoError(t, err)
@@ -1206,6 +1219,7 @@ func TestPeerConnection_TransceiverDirection(t *testing.T) {
_, err = pc.AddTransceiverFromTrack(track, []RTPTransceiverInit{
{Direction: dir},
}...)
return err
}
@@ -1213,6 +1227,7 @@ func TestPeerConnection_TransceiverDirection(t *testing.T) {
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: dir},
)
return err
}
@@ -1424,17 +1439,19 @@ a=sendonly
a=rtpmap:125 H264/90000
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
`
m := MediaEngine{}
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 94,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", nil},
PayloadType: 98,
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", nil,
},
PayloadType: 98,
}, RTPCodecTypeVideo))
api := NewAPI(WithMediaEngine(&m))
api := NewAPI(WithMediaEngine(&mediaEngine))
pc, err := api.NewPeerConnection(Configuration{})
assert.NoError(t, err)
assert.NoError(t, pc.SetRemoteDescription(SessionDescription{
@@ -1483,17 +1500,19 @@ a=rtpmap:98 H264/90000
a=fmtp:98 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=sendonly
`
m := MediaEngine{}
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
mediaEngine := MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeVP8, 90000, 0, "", nil},
PayloadType: 94,
}, RTPCodecTypeVideo))
assert.NoError(t, m.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", nil},
PayloadType: 98,
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{
MimeTypeH264, 90000, 0, "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", nil,
},
PayloadType: 98,
}, RTPCodecTypeVideo))
api := NewAPI(WithMediaEngine(&m))
api := NewAPI(WithMediaEngine(&mediaEngine))
pc, err := api.NewPeerConnection(Configuration{})
assert.NoError(t, err)
assert.NoError(t, pc.SetRemoteDescription(SessionDescription{
@@ -1567,7 +1586,10 @@ a=ssrc:1455629982 cname:{61fd3093-0326-4b12-8258-86bdc1fe677a}
assert.NoError(t, err)
assert.NoError(t, peerConnection.SetRemoteDescription(SessionDescription{Type: SDPTypeOffer, SDP: remoteSDP}))
assert.Equal(t, RTPTransceiverDirectionInactive, peerConnection.rtpTransceivers[0].direction.Load().(RTPTransceiverDirection)) //nolint:forcetypeassert
assert.Equal(
t, RTPTransceiverDirectionInactive,
peerConnection.rtpTransceivers[0].direction.Load().(RTPTransceiverDirection), //nolint:forcetypeassert
)
assert.NoError(t, peerConnection.Close())
}
@@ -1643,7 +1665,7 @@ func TestPeerConnectionDeadlock(t *testing.T) {
}
// Assert that by default NULL Ciphers aren't enabled. Even if
// the remote Peer Requests a NULL Cipher we should fail
// the remote Peer Requests a NULL Cipher we should fail.
func TestPeerConnectionNoNULLCipherDefault(t *testing.T) {
settingEngine := SettingEngine{}
settingEngine.SetSRTPProtectionProfiles(dtls.SRTP_NULL_HMAC_SHA1_80, dtls.SRTP_NULL_HMAC_SHA1_32)
@@ -1724,12 +1746,16 @@ a=ssrc:29586368 cname:janus
mediaEngine := &MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 96,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
},
PayloadType: 96,
}, RTPCodecTypeVideo))
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 111,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
},
PayloadType: 111,
}, RTPCodecTypeAudio))
api := NewAPI(WithMediaEngine(mediaEngine))
@@ -1776,12 +1802,16 @@ func TestTranceiverMediaStreamIdentification(t *testing.T) {
mediaEngine := &MediaEngine{}
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 96,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeVP8, ClockRate: 90000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
},
PayloadType: 96,
}, RTPCodecTypeVideo))
assert.NoError(t, mediaEngine.RegisterCodec(RTPCodecParameters{
RTPCodecCapability: RTPCodecCapability{MimeType: MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil},
PayloadType: 111,
RTPCodecCapability: RTPCodecCapability{
MimeType: MimeTypeOpus, ClockRate: 48000, Channels: 0, SDPFmtpLine: "", RTCPFeedback: nil,
},
PayloadType: 111,
}, RTPCodecTypeAudio))
api := NewAPI(WithMediaEngine(mediaEngine))

View File

@@ -45,7 +45,7 @@ Integration test for bi-directional peers
This asserts we can send RTP and RTCP both ways, and blocks until
each side gets something (and asserts payload contents)
*/
// nolint: gocyclo
//nolint:gocyclo,cyclop
func TestPeerConnection_Media_Sample(t *testing.T) {
const (
expectedTrackID = "video"
@@ -77,12 +77,18 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
pcAnswer.OnTrack(func(track *TrackRemote, receiver *RTPReceiver) {
if track.ID() != expectedTrackID {
trackMetadataValid <- fmt.Errorf("%w: expected(%s) actual(%s)", errIncomingTrackIDInvalid, expectedTrackID, track.ID())
trackMetadataValid <- fmt.Errorf(
"%w: expected(%s) actual(%s)", errIncomingTrackIDInvalid, expectedTrackID, track.ID(),
)
return
}
if track.StreamID() != expectedStreamID {
trackMetadataValid <- fmt.Errorf("%w: expected(%s) actual(%s)", errIncomingTrackLabelInvalid, expectedStreamID, track.StreamID())
trackMetadataValid <- fmt.Errorf(
"%w: expected(%s) actual(%s)", errIncomingTrackLabelInvalid, expectedStreamID, track.StreamID(),
)
return
}
close(trackMetadataValid)
@@ -90,14 +96,18 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
go func() {
for {
time.Sleep(time.Millisecond * 100)
if routineErr := pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{SenderSSRC: uint32(track.SSRC()), MediaSSRC: uint32(track.SSRC())}}); routineErr != nil {
if routineErr := pcAnswer.WriteRTCP([]rtcp.Packet{&rtcp.RapidResynchronizationRequest{
SenderSSRC: uint32(track.SSRC()), MediaSSRC: uint32(track.SSRC()),
}}); routineErr != nil {
awaitRTCPReceiverSend <- routineErr
return
}
select {
case <-awaitRTCPSenderRecv:
close(awaitRTCPReceiverSend)
return
default:
}
@@ -118,6 +128,7 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
p, _, routineErr := track.ReadRTP()
if routineErr != nil {
close(awaitRTPRecvClosed)
return
} else if bytes.Equal(p.Payload, []byte{0x10, 0x00}) && !haveClosedAwaitRTPRecv {
haveClosedAwaitRTPRecv = true
@@ -126,7 +137,9 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
}
})
vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, expectedTrackID, expectedStreamID)
vp8Track, err := NewTrackLocalStaticSample(
RTPCodecCapability{MimeType: MimeTypeVP8}, expectedTrackID, expectedStreamID,
)
if err != nil {
t.Fatal(err)
}
@@ -148,6 +161,7 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
select {
case <-awaitRTPRecv:
close(awaitRTPSend)
return
default:
}
@@ -159,13 +173,18 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
for {
time.Sleep(time.Millisecond * 100)
if routineErr := pcOffer.WriteRTCP([]rtcp.Packet{&rtcp.PictureLossIndication{SenderSSRC: uint32(parameters.Encodings[0].SSRC), MediaSSRC: uint32(parameters.Encodings[0].SSRC)}}); routineErr != nil {
if routineErr := pcOffer.WriteRTCP([]rtcp.Packet{
&rtcp.PictureLossIndication{
SenderSSRC: uint32(parameters.Encodings[0].SSRC), MediaSSRC: uint32(parameters.Encodings[0].SSRC),
},
}); routineErr != nil {
awaitRTCPSenderSend <- routineErr
}
select {
case <-awaitRTCPReceiverRecv:
close(awaitRTCPSenderSend)
return
default:
}
@@ -202,15 +221,12 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
<-awaitRTPRecvClosed
}
/*
PeerConnection should be able to be torn down at anytime
This test adds an input track and asserts
* OnTrack doesn't fire since no video packets will arrive
* No goroutine leaks
* No deadlocks on shutdown
*/
func TestPeerConnection_Media_Shutdown(t *testing.T) {
// PeerConnection should be able to be torn down at anytime
// This test adds an input track and asserts
// OnTrack doesn't fire since no video packets will arrive
// No goroutine leaks
// No deadlocks on shutdown.
func TestPeerConnection_Media_Shutdown(t *testing.T) { //nolint:cyclop
iceCompleteAnswer := make(chan struct{})
iceCompleteOffer := make(chan struct{})
@@ -225,12 +241,18 @@ func TestPeerConnection_Media_Shutdown(t *testing.T) {
t.Fatal(err)
}
_, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err = pcOffer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
if err != nil {
t.Fatal(err)
}
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeAudio, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err = pcAnswer.AddTransceiverFromKind(
RTPCodecTypeAudio,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
if err != nil {
t.Fatal(err)
}
@@ -305,12 +327,10 @@ func TestPeerConnection_Media_Shutdown(t *testing.T) {
onTrackFiredLock.Unlock()
}
/*
Integration test for behavior around media and disconnected peers
// Integration test for behavior around media and disconnected peers
// Sending RTP and RTCP to a disconnected Peer shouldn't return an error.
* Sending RTP and RTCP to a disconnected Peer shouldn't return an error
*/
func TestPeerConnection_Media_Disconnected(t *testing.T) {
func TestPeerConnection_Media_Disconnected(t *testing.T) { //nolint:cyclop
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -320,8 +340,8 @@ func TestPeerConnection_Media_Disconnected(t *testing.T) {
s := SettingEngine{}
s.SetICETimeouts(time.Second/2, time.Second/2, time.Second/8)
m := &MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
mediaEngine := &MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
pcOffer, pcAnswer, wan := createVNetPair(t, nil)
@@ -406,7 +426,7 @@ func (u *undeclaredSsrcLoggerFactory) NewLogger(string) logging.LeveledLogger {
return &undeclaredSsrcLogger{u.unhandledSimulcastError}
}
// Filter SSRC lines
// Filter SSRC lines.
func filterSsrc(offer string) (filteredSDP string) {
scanner := bufio.NewScanner(strings.NewReader(offer))
for scanner.Scan() {
@@ -417,11 +437,12 @@ func filterSsrc(offer string) (filteredSDP string) {
filteredSDP += l + "\n"
}
return
}
// If a SessionDescription has a single media section and no SSRC
// assume that it is meant to handle all RTP packets
// assume that it is meant to handle all RTP packets.
func TestUndeclaredSSRC(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -465,19 +486,19 @@ func TestUndeclaredSSRC(t *testing.T) {
assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription()))
sendVideoUntilDone(onTrackFired, t, []*TrackLocalStaticSample{vp8Writer})
sendVideoUntilDone(t, onTrackFired, []*TrackLocalStaticSample{vp8Writer})
closePairNow(t, pcOffer, pcAnswer)
})
t.Run("Has RID", func(t *testing.T) {
unhandledSimulcastError := make(chan struct{})
m := &MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
mediaEngine := &MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(SettingEngine{
LoggerFactory: &undeclaredSsrcLoggerFactory{unhandledSimulcastError},
}), WithMediaEngine(m)).newPair(Configuration{})
}), WithMediaEngine(mediaEngine)).newPair(Configuration{})
assert.NoError(t, err)
vp8Writer, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2")
@@ -506,7 +527,7 @@ func TestUndeclaredSSRC(t *testing.T) {
assert.NoError(t, pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription()))
sendVideoUntilDone(unhandledSimulcastError, t, []*TrackLocalStaticSample{vp8Writer})
sendVideoUntilDone(t, unhandledSimulcastError, []*TrackLocalStaticSample{vp8Writer})
closePairNow(t, pcOffer, pcAnswer)
})
}
@@ -755,7 +776,10 @@ func TestAddTransceiverFromTrackFailsRecvOnly(t *testing.T) {
}
track, err := NewTrackLocalStaticSample(
RTPCodecCapability{MimeType: MimeTypeH264, SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f"},
RTPCodecCapability{
MimeType: MimeTypeH264,
SDPFmtpLine: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f",
},
"track-id",
"track-label",
)
@@ -776,9 +800,15 @@ func TestAddTransceiverFromTrackFailsRecvOnly(t *testing.T) {
}
func TestPlanBMediaExchange(t *testing.T) {
runTest := func(trackCount int, t *testing.T) {
runTest := func(t *testing.T, trackCount int) {
t.Helper()
addSingleTrack := func(p *PeerConnection) *TrackLocalStaticSample {
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, fmt.Sprintf("video-%d", util.RandUint32()), fmt.Sprintf("video-%d", util.RandUint32()))
track, err := NewTrackLocalStaticSample(
RTPCodecCapability{MimeType: MimeTypeVP8},
fmt.Sprintf("video-%d", util.RandUint32()),
fmt.Sprintf("video-%d", util.RandUint32()),
)
assert.NoError(t, err)
_, err = p.AddTrack(track)
@@ -838,16 +868,16 @@ func TestPlanBMediaExchange(t *testing.T) {
defer report()
t.Run("Single Track", func(t *testing.T) {
runTest(1, t)
runTest(t, 1)
})
t.Run("Multi Track", func(t *testing.T) {
runTest(2, t)
runTest(t, 2)
})
}
// TestPeerConnection_Start_Only_Negotiated_Senders tests that only
// the current negotiated transceivers senders provided in an
// offer/answer are started
// offer/answer are started.
func TestPeerConnection_Start_Only_Negotiated_Senders(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -903,15 +933,17 @@ func TestPeerConnection_Start_Only_Negotiated_Senders(t *testing.T) {
// TestPeerConnection_Start_Right_Receiver tests that the right
// receiver (the receiver which transceiver has the same media section as the track)
// is started for the specified track
// is started for the specified track.
func TestPeerConnection_Start_Right_Receiver(t *testing.T) {
isTransceiverReceiverStarted := func(pc *PeerConnection, mid string) (bool, error) {
for _, transceiver := range pc.GetTransceivers() {
if transceiver.Mid() != mid {
continue
}
return transceiver.Receiver() != nil && transceiver.Receiver().haveReceived(), nil
}
return false, fmt.Errorf("%w: %q", errNoTransceiverwithMid, mid)
}
@@ -924,7 +956,10 @@ func TestPeerConnection_Start_Right_Receiver(t *testing.T) {
pcOffer, pcAnswer, err := newPair()
require.NoError(t, err)
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err = pcAnswer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
assert.NoError(t, err)
track1, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion1")
@@ -960,7 +995,10 @@ func TestPeerConnection_Start_Right_Receiver(t *testing.T) {
_, err = pcOffer.AddTransceiverFromTrack(track1)
assert.NoError(t, err)
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err = pcAnswer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
assert.NoError(t, err)
assert.NoError(t, signalPair(pcOffer, pcAnswer))
@@ -980,7 +1018,7 @@ func TestPeerConnection_Start_Right_Receiver(t *testing.T) {
closePairNow(t, pcOffer, pcAnswer)
}
func TestPeerConnection_Simulcast_Probe(t *testing.T) {
func TestPeerConnection_Simulcast_Probe(t *testing.T) { //nolint:cyclop
lim := test.TimeOut(time.Second * 30) //nolint
defer lim.Stop()
@@ -1044,13 +1082,13 @@ func TestPeerConnection_Simulcast_Probe(t *testing.T) {
t.Run("Break NonSimulcast", func(t *testing.T) {
unhandledSimulcastError := make(chan struct{})
m := &MediaEngine{}
assert.NoError(t, m.RegisterDefaultCodecs())
assert.NoError(t, ConfigureSimulcastExtensionHeaders(m))
mediaEngine := &MediaEngine{}
assert.NoError(t, mediaEngine.RegisterDefaultCodecs())
assert.NoError(t, ConfigureSimulcastExtensionHeaders(mediaEngine))
pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(SettingEngine{
LoggerFactory: &undeclaredSsrcLoggerFactory{unhandledSimulcastError},
}), WithMediaEngine(m)).newPair(Configuration{})
}), WithMediaEngine(mediaEngine)).newPair(Configuration{})
assert.NoError(t, err)
firstTrack, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "firstTrack", "firstTrack")
@@ -1074,6 +1112,7 @@ func TestPeerConnection_Simulcast_Probe(t *testing.T) {
shouldDiscard = !shouldDiscard
} else if strings.HasPrefix(scanner.Text(), "a=group:BUNDLE") {
filtered += "a=group:BUNDLE 1 2\r\n"
continue
}
@@ -1081,6 +1120,7 @@ func TestPeerConnection_Simulcast_Probe(t *testing.T) {
filtered += scanner.Text() + "\r\n"
}
}
return
}))
@@ -1142,6 +1182,7 @@ func TestPeerConnection_Simulcast_Probe(t *testing.T) {
// Assert that CreateOffer returns an error for a RTPSender with no codecs
// pion/webrtc#1702
// .
func TestPeerConnection_CreateOffer_NoCodecs(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -1149,9 +1190,9 @@ func TestPeerConnection_CreateOffer_NoCodecs(t *testing.T) {
report := test.CheckRoutines(t)
defer report()
m := &MediaEngine{}
mediaEngine := &MediaEngine{}
pc, err := NewAPI(WithMediaEngine(m)).NewPeerConnection(Configuration{})
pc, err := NewAPI(WithMediaEngine(mediaEngine)).NewPeerConnection(Configuration{})
assert.NoError(t, err)
track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion")
@@ -1166,7 +1207,7 @@ func TestPeerConnection_CreateOffer_NoCodecs(t *testing.T) {
assert.NoError(t, pc.Close())
}
// Assert that AddTrack is thread-safe
// Assert that AddTrack is thread-safe.
func TestPeerConnection_RaceReplaceTrack(t *testing.T) {
pc, err := NewPeerConnection(Configuration{})
assert.NoError(t, err)
@@ -1176,6 +1217,7 @@ func TestPeerConnection_RaceReplaceTrack(t *testing.T) {
assert.NoError(t, err)
_, err = pc.AddTrack(track)
assert.NoError(t, err)
return track
}
@@ -1203,6 +1245,7 @@ func TestPeerConnection_RaceReplaceTrack(t *testing.T) {
for _, t := range pc.GetTransceivers() {
if t.Sender() != nil && t.Sender().Track() == track {
have = true
break
}
}
@@ -1214,7 +1257,7 @@ func TestPeerConnection_RaceReplaceTrack(t *testing.T) {
assert.NoError(t, pc.Close())
}
func TestPeerConnection_Simulcast(t *testing.T) {
func TestPeerConnection_Simulcast(t *testing.T) { //nolint:cyclop
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -1227,13 +1270,19 @@ func TestPeerConnection_Simulcast(t *testing.T) {
pcOffer, pcAnswer, err := newPair()
assert.NoError(t, err)
vp8WriterA, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[0]))
vp8WriterA, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[0]),
)
assert.NoError(t, err)
vp8WriterB, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[1]))
vp8WriterB, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[1]),
)
assert.NoError(t, err)
vp8WriterC, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[2]))
vp8WriterC, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[2]),
)
assert.NoError(t, err)
sender, err := pcOffer.AddTrack(vp8WriterA)
@@ -1247,6 +1296,8 @@ func TestPeerConnection_Simulcast(t *testing.T) {
ridMap := map[string]int{}
assertRidCorrect := func(t *testing.T) {
t.Helper()
ridMapLock.Lock()
defer ridMapLock.Unlock()
@@ -1261,6 +1312,7 @@ func TestPeerConnection_Simulcast(t *testing.T) {
defer ridMapLock.Unlock()
ridCount := len(ridMap)
return ridCount == 3
}
@@ -1279,9 +1331,9 @@ func TestPeerConnection_Simulcast(t *testing.T) {
for _, extension := range parameters.HeaderExtensions {
switch extension.URI {
case sdp.SDESMidURI:
midID = uint8(extension.ID)
midID = uint8(extension.ID) //nolint:gosec // G115
case sdp.SDESRTPStreamIDURI:
ridID = uint8(extension.ID)
ridID = uint8(extension.ID) //nolint:gosec // G115
}
}
assert.NotZero(t, midID)
@@ -1337,13 +1389,19 @@ func TestPeerConnection_Simulcast(t *testing.T) {
pcOffer, pcAnswer, err := newPair()
assert.NoError(t, err)
vp8WriterA, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[0]))
vp8WriterA, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[0]),
)
assert.NoError(t, err)
vp8WriterB, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[1]))
vp8WriterB, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[1]),
)
assert.NoError(t, err)
vp8WriterC, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[2]))
vp8WriterC, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[2]),
)
assert.NoError(t, err)
sender, err := pcOffer.AddTrack(vp8WriterA)
@@ -1364,9 +1422,9 @@ func TestPeerConnection_Simulcast(t *testing.T) {
for _, extension := range sender.GetParameters().HeaderExtensions {
switch extension.URI {
case sdp.SDESMidURI:
midID = uint8(extension.ID)
midID = uint8(extension.ID) //nolint:gosec // G115
case sdp.SDESRTPStreamIDURI:
ridID = uint8(extension.ID)
ridID = uint8(extension.ID) //nolint:gosec // G115
}
}
assert.NotZero(t, midID)
@@ -1423,7 +1481,7 @@ func (s *simulcastTestTrackLocal) WriteRTP(pkt *rtp.Packet) error {
return util.FlattenErrs(writeErrs)
}
func TestPeerConnection_Simulcast_RTX(t *testing.T) {
func TestPeerConnection_Simulcast_RTX(t *testing.T) { //nolint:cyclop
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -1434,10 +1492,14 @@ func TestPeerConnection_Simulcast_RTX(t *testing.T) {
pcOffer, pcAnswer, err := newPair()
assert.NoError(t, err)
vp8WriterAStatic, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[0]))
vp8WriterAStatic, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[0]),
)
assert.NoError(t, err)
vp8WriterBStatic, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[1]))
vp8WriterBStatic, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion2", WithRTPStreamID(rids[1]),
)
assert.NoError(t, err)
vp8WriterA, vp8WriterB := &simulcastTestTrackLocal{vp8WriterAStatic}, &simulcastTestTrackLocal{vp8WriterBStatic}
@@ -1452,6 +1514,8 @@ func TestPeerConnection_Simulcast_RTX(t *testing.T) {
ridMap := map[string]int{}
assertRidCorrect := func(t *testing.T) {
t.Helper()
ridMapLock.Lock()
defer ridMapLock.Unlock()
@@ -1466,6 +1530,7 @@ func TestPeerConnection_Simulcast_RTX(t *testing.T) {
defer ridMapLock.Unlock()
ridCount := len(ridMap)
return ridCount == 2
}
@@ -1501,11 +1566,11 @@ func TestPeerConnection_Simulcast_RTX(t *testing.T) {
for _, extension := range parameters.HeaderExtensions {
switch extension.URI {
case sdp.SDESMidURI:
midID = uint8(extension.ID)
midID = uint8(extension.ID) //nolint:gosec // G115
case sdp.SDESRTPStreamIDURI:
ridID = uint8(extension.ID)
ridID = uint8(extension.ID) //nolint:gosec // G115
case sdp.SDESRepairRTPStreamIDURI:
rsid = uint8(extension.ID)
rsid = uint8(extension.ID) //nolint:gosec // G115
}
}
assert.NotZero(t, midID)
@@ -1516,6 +1581,7 @@ func TestPeerConnection_Simulcast_RTX(t *testing.T) {
// Original chrome sdp contains no ssrc info https://pastebin.com/raw/JTjX6zg6
re := regexp.MustCompile("(?m)[\r\n]+^.*a=ssrc.*$")
res := re.ReplaceAllString(sdp, "")
return res
})
assert.NoError(t, err)
@@ -1532,7 +1598,7 @@ func TestPeerConnection_Simulcast_RTX(t *testing.T) {
SequenceNumber: sequenceNumber,
PayloadType: 96,
Padding: true,
SSRC: uint32(i + 1),
SSRC: uint32(i + 1), //nolint:gosec // G115
},
Payload: []byte{0x00, 0x02},
}
@@ -1551,7 +1617,7 @@ func TestPeerConnection_Simulcast_RTX(t *testing.T) {
Version: 2,
SequenceNumber: sequenceNumber,
PayloadType: 96,
SSRC: uint32(i + 1),
SSRC: uint32(i + 1), //nolint:gosec // G115
},
Payload: []byte{0x00},
}
@@ -1574,7 +1640,7 @@ func TestPeerConnection_Simulcast_RTX(t *testing.T) {
Version: 2,
SequenceNumber: sequenceNumber,
PayloadType: 97,
SSRC: uint32(100 + j),
SSRC: uint32(100 + j), //nolint:gosec // G115
},
Payload: []byte{0x00, 0x00, 0x00, 0x00, 0x00},
}
@@ -1595,7 +1661,7 @@ func TestPeerConnection_Simulcast_RTX(t *testing.T) {
Version: 2,
SequenceNumber: sequenceNumber,
PayloadType: 96,
SSRC: uint32(i + 1),
SSRC: uint32(i + 1), //nolint:gosec // G115
},
Payload: []byte{0x00},
}
@@ -1653,19 +1719,25 @@ func TestPeerConnection_Simulcast_NoDataChannel(t *testing.T) {
go func() {
defer wg.Done()
vp8WriterA, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("a"))
vp8WriterA, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("a"),
)
assert.NoError(t, err)
sender, err := pcSender.AddTrack(vp8WriterA)
assert.NoError(t, err)
assert.NotNil(t, sender)
vp8WriterB, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("b"))
vp8WriterB, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("b"),
)
assert.NoError(t, err)
err = sender.AddEncoding(vp8WriterB)
assert.NoError(t, err)
vp8WriterC, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("c"))
vp8WriterC, err := NewTrackLocalStaticRTP(
RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion", WithRTPStreamID("c"),
)
assert.NoError(t, err)
err = sender.AddEncoding(vp8WriterC)
assert.NoError(t, err)
@@ -1675,11 +1747,11 @@ func TestPeerConnection_Simulcast_NoDataChannel(t *testing.T) {
for _, extension := range parameters.HeaderExtensions {
switch extension.URI {
case sdp.SDESMidURI:
midID = uint8(extension.ID)
midID = uint8(extension.ID) //nolint:gosec // G115
case sdp.SDESRTPStreamIDURI:
ridID = uint8(extension.ID)
ridID = uint8(extension.ID) //nolint:gosec // G115
case sdp.SDESRepairRTPStreamIDURI:
rsidID = uint8(extension.ID)
rsidID = uint8(extension.ID) //nolint:gosec // G115
}
}
assert.NotZero(t, midID)
@@ -1740,7 +1812,7 @@ func TestPeerConnection_Simulcast_NoDataChannel(t *testing.T) {
// Check that PayloadType of 0 is handled correctly. At one point
// we incorrectly assumed 0 meant an invalid stream and wouldn't update things
// properly
// properly.
func TestPeerConnection_Zero_PayloadType(t *testing.T) {
lim := test.TimeOut(time.Second * 5)
defer lim.Stop()
@@ -1775,7 +1847,9 @@ func TestPeerConnection_Zero_PayloadType(t *testing.T) {
case <-trackFired:
return
case <-ticker.C:
if routineErr := audioTrack.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); routineErr != nil {
if routineErr := audioTrack.WriteSample(
media.Sample{Data: []byte{0x00}, Duration: time.Second},
); routineErr != nil {
fmt.Println(routineErr)
}
}
@@ -1786,8 +1860,8 @@ func TestPeerConnection_Zero_PayloadType(t *testing.T) {
}
// Assert that NACKs work E2E with no extra configuration. If media is sent over a lossy connection
// the user gets retransmitted RTP packets with no extra configuration
func Test_PeerConnection_RTX_E2E(t *testing.T) {
// the user gets retransmitted RTP packets with no extra configuration.
func Test_PeerConnection_RTX_E2E(t *testing.T) { //nolint:cyclop
defer test.TimeOut(time.Second * 30).Stop()
pcOffer, pcAnswer, wan := createVNetPair(t, nil)

View File

@@ -27,7 +27,9 @@ import (
"github.com/stretchr/testify/require"
)
func sendVideoUntilDone(done <-chan struct{}, t *testing.T, tracks []*TrackLocalStaticSample) {
func sendVideoUntilDone(t *testing.T, done <-chan struct{}, tracks []*TrackLocalStaticSample) {
t.Helper()
for {
select {
case <-time.After(20 * time.Millisecond):
@@ -64,6 +66,7 @@ func sdpMidHasSsrc(offer SessionDescription, mid string, ssrc SSRC) bool {
return true
}
}
return false
}
@@ -139,19 +142,18 @@ func TestPeerConnection_Renegotiation_AddRecvonlyTransceiver(t *testing.T) {
assert.NoError(t, signalPair(pcOffer, pcAnswer))
}
sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{localTrack})
sendVideoUntilDone(t, onTrackFired.Done(), []*TrackLocalStaticSample{localTrack})
closePairNow(t, pcOffer, pcAnswer)
})
}
}
/*
* Assert the following behaviors
* - We are able to call AddTrack after signaling
* - OnTrack is NOT called on the other side until after SetRemoteDescription
* - We are able to re-negotiate and AddTrack is properly called
*/
// Assert the following behaviors
//
// - We are able to call AddTrack after signaling
// - OnTrack is NOT called on the other side until after SetRemoteDescription
// - We are able to re-negotiate and AddTrack is properly called.
func TestPeerConnection_Renegotiation_AddTrack(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -175,7 +177,10 @@ func TestPeerConnection_Renegotiation_AddTrack(t *testing.T) {
assert.NoError(t, signalPair(pcOffer, pcAnswer))
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err = pcAnswer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
assert.NoError(t, err)
vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar")
@@ -210,15 +215,18 @@ func TestPeerConnection_Renegotiation_AddTrack(t *testing.T) {
pcOffer.ops.Done()
assert.Equal(t, 1, len(vp8Track.rtpTrack.bindings))
sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track})
sendVideoUntilDone(t, onTrackFired.Done(), []*TrackLocalStaticSample{vp8Track})
closePairNow(t, pcOffer, pcAnswer)
}
// Assert that adding tracks across multiple renegotiations performs as expected
// Assert that adding tracks across multiple renegotiations performs as expected.
func TestPeerConnection_Renegotiation_AddTrack_Multiple(t *testing.T) {
addTrackWithLabel := func(trackID string, pcOffer, pcAnswer *PeerConnection) *TrackLocalStaticSample {
_, err := pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err := pcAnswer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
assert.NoError(t, err)
track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, trackID, trackID)
@@ -256,7 +264,7 @@ func TestPeerConnection_Renegotiation_AddTrack_Multiple(t *testing.T) {
for i := range trackIDs {
outboundTracks = append(outboundTracks, addTrackWithLabel(trackIDs[i], pcOffer, pcAnswer))
assert.NoError(t, signalPair(pcOffer, pcAnswer))
sendVideoUntilDone(onTrackChan, t, outboundTracks)
sendVideoUntilDone(t, onTrackChan, outboundTracks)
}
closePairNow(t, pcOffer, pcAnswer)
@@ -296,7 +304,10 @@ func TestPeerConnection_Renegotiation_AddTrack_Rename(t *testing.T) {
atomicRemoteTrack.Store(track)
})
_, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err = pcOffer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
assert.NoError(t, err)
vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo1", "bar1")
assert.NoError(t, err)
@@ -311,7 +322,7 @@ func TestPeerConnection_Renegotiation_AddTrack_Rename(t *testing.T) {
haveRenegotiated.set(true)
assert.NoError(t, signalPair(pcOffer, pcAnswer))
sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track})
sendVideoUntilDone(t, onTrackFired.Done(), []*TrackLocalStaticSample{vp8Track})
closePairNow(t, pcOffer, pcAnswer)
@@ -323,7 +334,7 @@ func TestPeerConnection_Renegotiation_AddTrack_Rename(t *testing.T) {
}
// TestPeerConnection_Transceiver_Mid tests that we'll provide the same
// transceiver for a media id on successive offer/answer
// transceiver for a media id on successive offer/answer.
func TestPeerConnection_Transceiver_Mid(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -375,7 +386,14 @@ func TestPeerConnection_Transceiver_Mid(t *testing.T) {
// Must have 3 media descriptions (2 video channels)
assert.Equal(t, len(offer.parsed.MediaDescriptions), 2)
assert.True(t, sdpMidHasSsrc(offer, "0", sender1.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.SDP: %s", "0", sender1.trackEncodings[0].ssrc, offer.SDP)
assert.True(
t,
sdpMidHasSsrc(offer, "0", sender1.trackEncodings[0].ssrc),
"Expected mid %q with ssrc %d, offer.SDP: %s",
"0",
sender1.trackEncodings[0].ssrc,
offer.SDP,
)
// Remove first track, must keep same number of media
// descriptions and same track ssrc for mid 1 as previous
@@ -387,7 +405,14 @@ func TestPeerConnection_Transceiver_Mid(t *testing.T) {
assert.Equal(t, len(offer.parsed.MediaDescriptions), 2)
assert.True(t, sdpMidHasSsrc(offer, "1", sender2.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.SDP: %s", "1", sender2.trackEncodings[0].ssrc, offer.SDP)
assert.True(
t,
sdpMidHasSsrc(offer, "1", sender2.trackEncodings[0].ssrc),
"Expected mid %q with ssrc %d, offer.SDP: %s",
"1",
sender2.trackEncodings[0].ssrc,
offer.SDP,
)
_, err = pcAnswer.CreateAnswer(nil)
assert.Equal(t, err, &rtcerr.InvalidStateError{Err: ErrIncorrectSignalingState})
@@ -412,8 +437,22 @@ func TestPeerConnection_Transceiver_Mid(t *testing.T) {
// We reuse the existing non-sending transceiver
assert.Equal(t, len(offer.parsed.MediaDescriptions), 2)
assert.True(t, sdpMidHasSsrc(offer, "0", sender3.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.sdp: %s", "0", sender3.trackEncodings[0].ssrc, offer.SDP)
assert.True(t, sdpMidHasSsrc(offer, "1", sender2.trackEncodings[0].ssrc), "Expected mid %q with ssrc %d, offer.sdp: %s", "1", sender2.trackEncodings[0].ssrc, offer.SDP)
assert.True(
t,
sdpMidHasSsrc(offer, "0", sender3.trackEncodings[0].ssrc),
"Expected mid %q with ssrc %d, offer.sdp: %s",
"0",
sender3.trackEncodings[0].ssrc,
offer.SDP,
)
assert.True(
t,
sdpMidHasSsrc(offer, "1", sender2.trackEncodings[0].ssrc),
"Expected mid %q with ssrc %d, offer.sdp: %s",
"1",
sender2.trackEncodings[0].ssrc,
offer.SDP,
)
closePairNow(t, pcOffer, pcAnswer)
}
@@ -440,7 +479,10 @@ func TestPeerConnection_Renegotiation_CodecChange(t *testing.T) {
sender1, err := pcOffer.AddTrack(track1)
require.NoError(t, err)
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err = pcAnswer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
require.NoError(t, err)
tracksCh := make(chan *TrackRemote)
@@ -450,6 +492,7 @@ func TestPeerConnection_Renegotiation_CodecChange(t *testing.T) {
for {
if _, _, readErr := track.ReadRTP(); errors.Is(readErr, io.EOF) {
tracksClosed <- struct{}{}
return
}
}
@@ -467,7 +510,7 @@ func TestPeerConnection_Renegotiation_CodecChange(t *testing.T) {
require.Equal(t, "0", transceivers[0].Mid())
ctx, cancel := context.WithCancel(context.Background())
go sendVideoUntilDone(ctx.Done(), t, []*TrackLocalStaticSample{track1})
go sendVideoUntilDone(t, ctx.Done(), []*TrackLocalStaticSample{track1})
remoteTrack1 := <-tracksCh
cancel()
@@ -492,7 +535,7 @@ func TestPeerConnection_Renegotiation_CodecChange(t *testing.T) {
require.Equal(t, "0", transceivers[0].Mid())
ctx, cancel = context.WithCancel(context.Background())
go sendVideoUntilDone(ctx.Done(), t, []*TrackLocalStaticSample{track2})
go sendVideoUntilDone(t, ctx.Done(), []*TrackLocalStaticSample{track2})
remoteTrack2 := <-tracksCh
cancel()
@@ -521,7 +564,10 @@ func TestPeerConnection_Renegotiation_RemoveTrack(t *testing.T) {
t.Fatal(err)
}
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err = pcAnswer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
assert.NoError(t, err)
vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar")
@@ -539,13 +585,14 @@ func TestPeerConnection_Renegotiation_RemoveTrack(t *testing.T) {
for {
if _, _, err := track.ReadRTP(); errors.Is(err, io.EOF) {
trackClosedFunc()
return
}
}
})
assert.NoError(t, signalPair(pcOffer, pcAnswer))
sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track})
sendVideoUntilDone(t, onTrackFired.Done(), []*TrackLocalStaticSample{vp8Track})
assert.NoError(t, pcOffer.RemoveTrack(sender))
assert.NoError(t, signalPair(pcOffer, pcAnswer))
@@ -574,8 +621,12 @@ func TestPeerConnection_RoleSwitch(t *testing.T) {
assert.NoError(t, signalPair(pcFirstOfferer, pcSecondOfferer))
// Add a new Track to the second offerer
// This asserts that it will match the ordering of the last RemoteDescription, but then also add new Transceivers to the end
_, err = pcFirstOfferer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
// This asserts that it will match the ordering of the last RemoteDescription,
// but then also add new Transceivers to the end.
_, err = pcFirstOfferer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
assert.NoError(t, err)
vp8Track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar")
@@ -585,14 +636,14 @@ func TestPeerConnection_RoleSwitch(t *testing.T) {
assert.NoError(t, err)
assert.NoError(t, signalPair(pcSecondOfferer, pcFirstOfferer))
sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{vp8Track})
sendVideoUntilDone(t, onTrackFired.Done(), []*TrackLocalStaticSample{vp8Track})
closePairNow(t, pcFirstOfferer, pcSecondOfferer)
}
// Assert that renegotiation doesn't attempt to gather ICE twice
// Before we would attempt to gather multiple times and would put
// the PeerConnection into a broken state
// the PeerConnection into a broken state.
func TestPeerConnection_Renegotiation_Trickle(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -681,7 +732,10 @@ func TestPeerConnection_Renegotiation_SetLocalDescription(t *testing.T) {
pcOffer.ops.Done()
pcAnswer.ops.Done()
_, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err = pcOffer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
assert.NoError(t, err)
localTrack, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "foo", "bar")
@@ -709,13 +763,13 @@ func TestPeerConnection_Renegotiation_SetLocalDescription(t *testing.T) {
assert.NoError(t, pcOffer.SetRemoteDescription(answer))
sendVideoUntilDone(onTrackFired.Done(), t, []*TrackLocalStaticSample{localTrack})
sendVideoUntilDone(t, onTrackFired.Done(), []*TrackLocalStaticSample{localTrack})
closePairNow(t, pcOffer, pcAnswer)
}
// Issue #346, don't start the SCTP Subsystem if the RemoteDescription doesn't contain one
// Before we would always start it, and re-negotiations would fail because SCTP was in flight
// Before we would always start it, and re-negotiations would fail because SCTP was in flight.
func TestPeerConnection_Renegotiation_NoApplication(t *testing.T) {
signalPairExcludeDataChannel := func(pcOffer, pcAnswer *PeerConnection) {
offer, err := pcOffer.CreateOffer(nil)
@@ -761,10 +815,16 @@ func TestPeerConnection_Renegotiation_NoApplication(t *testing.T) {
}
})
_, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionSendrecv})
_, err = pcOffer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionSendrecv},
)
assert.NoError(t, err)
_, err = pcAnswer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionSendrecv})
_, err = pcAnswer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionSendrecv},
)
assert.NoError(t, err)
signalPairExcludeDataChannel(pcOffer, pcAnswer)
@@ -847,7 +907,7 @@ func TestAddDataChannelDuringRenegotiation(t *testing.T) {
closePairNow(t, pcOffer, pcAnswer)
}
// Assert that CreateDataChannel fires OnNegotiationNeeded
// Assert that CreateDataChannel fires OnNegotiationNeeded.
func TestNegotiationCreateDataChannel(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -963,7 +1023,7 @@ func TestNegotiationNeededStressOneSided(t *testing.T) {
}
// TestPeerConnection_Renegotiation_DisableTrack asserts that if a remote track is set inactive
// that locally it goes inactive as well
// that locally it goes inactive as well.
func TestPeerConnection_Renegotiation_DisableTrack(t *testing.T) {
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -1020,6 +1080,7 @@ func TestPeerConnection_Renegotiation_Simulcast(t *testing.T) {
for _, rid := range rids {
sessionDescription += "a=" + sdpAttributeRid + ":" + rid + " send\r\n"
}
return sessionDescription + "a=simulcast:send " + strings.Join(rids, ";") + "\r\n"
}
@@ -1046,7 +1107,7 @@ func TestPeerConnection_Renegotiation_Simulcast(t *testing.T) {
for ssrc, rid := range rids {
header := &rtp.Header{
Version: 2,
SSRC: uint32(ssrc + 1),
SSRC: uint32(ssrc + 1), //nolint:gosec // G115
SequenceNumber: sequenceNumber,
PayloadType: 96,
}
@@ -1060,6 +1121,8 @@ func TestPeerConnection_Renegotiation_Simulcast(t *testing.T) {
}
assertTracksClosed := func(t *testing.T) {
t.Helper()
trackMapLock.Lock()
defer trackMapLock.Unlock()
@@ -1098,6 +1161,7 @@ func TestPeerConnection_Renegotiation_Simulcast(t *testing.T) {
assert.NoError(t, pcOffer.RemoveTrack(rtpTransceiver.Sender()))
assert.NoError(t, signalPairWithModification(pcOffer, pcAnswer, func(sessionDescription string) string {
sessionDescription = strings.SplitAfter(sessionDescription, "a=end-of-candidates\r\n")[0]
return sessionDescription
}))
@@ -1140,6 +1204,7 @@ func TestPeerConnection_Renegotiation_Simulcast(t *testing.T) {
sessionDescription += l + "\n"
}
return signalWithRids(sessionDescription, newRids)
}))
@@ -1245,9 +1310,15 @@ func TestPeerConnection_Renegotiation_MidConflict(t *testing.T) {
_, err = offerPC.CreateDataChannel("test", nil)
assert.NoError(t, err)
_, err = offerPC.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionSendonly})
_, err = offerPC.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionSendonly},
)
assert.NoError(t, err)
_, err = offerPC.AddTransceiverFromKind(RTPCodecTypeAudio, RTPTransceiverInit{Direction: RTPTransceiverDirectionSendonly})
_, err = offerPC.AddTransceiverFromKind(
RTPCodecTypeAudio,
RTPTransceiverInit{Direction: RTPTransceiverDirectionSendonly},
)
assert.NoError(t, err)
offer, err := offerPC.CreateOffer(nil)
@@ -1260,10 +1331,16 @@ func TestPeerConnection_Renegotiation_MidConflict(t *testing.T) {
assert.NoError(t, offerPC.SetRemoteDescription(answer))
assert.Equal(t, SignalingStateStable, offerPC.SignalingState())
tr, err := offerPC.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionSendonly})
tr, err := offerPC.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionSendonly},
)
assert.NoError(t, err)
assert.NoError(t, tr.SetMid("3"))
_, err = offerPC.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionSendrecv})
_, err = offerPC.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionSendrecv},
)
assert.NoError(t, err)
_, err = offerPC.CreateOffer(nil)
assert.NoError(t, err)
@@ -1316,7 +1393,7 @@ func TestPeerConnection_Regegotiation_AnswerAddsTrack(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
go sendVideoUntilDone(ctx.Done(), t, []*TrackLocalStaticSample{vp8Track})
go sendVideoUntilDone(t, ctx.Done(), []*TrackLocalStaticSample{vp8Track})
<-tracksCh
cancel()
@@ -1340,7 +1417,10 @@ func TestNegotiationNeededWithRecvonlyTrack(t *testing.T) {
wg.Add(1)
pcAnswer.OnNegotiationNeeded(wg.Done)
_, err = pcOffer.AddTransceiverFromKind(RTPCodecTypeVideo, RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
_, err = pcOffer.AddTransceiverFromKind(
RTPCodecTypeVideo,
RTPTransceiverInit{Direction: RTPTransceiverDirectionRecvonly},
)
if err != nil {
t.Fatal(err)
}

View File

@@ -32,7 +32,11 @@ func newPair() (pcOffer *PeerConnection, pcAnswer *PeerConnection, err error) {
return pca, pcb, nil
}
func signalPairWithModification(pcOffer *PeerConnection, pcAnswer *PeerConnection, modificationFunc func(string) string) error {
func signalPairWithModification(
pcOffer *PeerConnection,
pcAnswer *PeerConnection,
modificationFunc func(string) string,
) error {
// Note(albrow): We need to create a data channel in order to trigger ICE
// candidate gathering in the background for the JavaScript/Wasm bindings. If
// we don't do this, the complete offer including ICE candidates will never be
@@ -65,11 +69,16 @@ func signalPairWithModification(pcOffer *PeerConnection, pcAnswer *PeerConnectio
return err
}
<-answerGatheringComplete
return pcOffer.SetRemoteDescription(*pcAnswer.LocalDescription())
}
func signalPair(pcOffer *PeerConnection, pcAnswer *PeerConnection) error {
return signalPairWithModification(pcOffer, pcAnswer, func(sessionDescription string) string { return sessionDescription })
return signalPairWithModification(
pcOffer,
pcAnswer,
func(sessionDescription string) string { return sessionDescription },
)
}
func offerMediaHasDirection(offer SessionDescription, kind RTPCodecType, direction RTPTransceiverDirection) bool {
@@ -81,9 +90,11 @@ func offerMediaHasDirection(offer SessionDescription, kind RTPCodecType, directi
for _, media := range parsed.MediaDescriptions {
if media.MediaName.Media == kind.String() {
_, exists := media.Attribute(direction.String())
return exists
}
}
return false
}
@@ -103,6 +114,7 @@ func untilConnectionState(state PeerConnectionState, peers ...*PeerConnection) *
p.OnConnectionStateChange(hdlr)
}
return &triggered
}
@@ -179,6 +191,7 @@ func TestPeerConnection_SetConfiguration(t *testing.T) {
err = pc.Close()
assert.Nil(t, err)
return pc, err
},
config: Configuration{},
@@ -231,6 +244,7 @@ func TestPeerConnection_SetConfiguration(t *testing.T) {
if err != nil {
return pc, err
}
return pc, nil
},
config: Configuration{
@@ -607,8 +621,8 @@ func TestMultipleCreateChannel(t *testing.T) {
closePairNow(t, pcOffer, pcAnswer)
}
// Assert that candidates are gathered by calling SetLocalDescription, not SetRemoteDescription
func TestGatherOnSetLocalDescription(t *testing.T) {
// Assert that candidates are gathered by calling SetLocalDescription, not SetRemoteDescription.
func TestGatherOnSetLocalDescription(t *testing.T) { //nolint:cyclop
lim := test.TimeOut(time.Second * 30)
defer lim.Stop()
@@ -678,7 +692,7 @@ func TestGatherOnSetLocalDescription(t *testing.T) {
closePairNow(t, pcOffer, pcAnswer)
}
// Assert that SetRemoteDescription handles invalid states
// Assert that SetRemoteDescription handles invalid states.
func TestSetRemoteDescriptionInvalid(t *testing.T) {
t.Run("local-offer+SetRemoteDescription(Offer)", func(t *testing.T) {
pc, err := NewPeerConnection(Configuration{})
@@ -738,7 +752,7 @@ func TestAddTransceiver(t *testing.T) {
}
}
// Assert that SCTPTransport -> DTLSTransport -> ICETransport works after connected
// Assert that SCTPTransport -> DTLSTransport -> ICETransport works after connected.
func TestTransportChain(t *testing.T) {
offer, answer, err := newPair()
assert.NoError(t, err)
@@ -752,7 +766,7 @@ func TestTransportChain(t *testing.T) {
closePairNow(t, offer, answer)
}
// Assert that the PeerConnection closes via DTLS (and not ICE)
// Assert that the PeerConnection closes via DTLS (and not ICE).
func TestDTLSClose(t *testing.T) {
lim := test.TimeOut(time.Second * 10)
defer lim.Stop()

View File

@@ -7,7 +7,7 @@ package webrtc
type PeerConnectionState int
const (
// PeerConnectionStateUnknown is the enum's zero-value
// PeerConnectionStateUnknown is the enum's zero-value.
PeerConnectionStateUnknown PeerConnectionState = iota
// PeerConnectionStateNew indicates that any of the ICETransports or

View File

@@ -10,7 +10,7 @@ import (
"io"
)
// H264Reader reads data from stream and constructs h264 nal units
// H264Reader reads data from stream and constructs h264 nal units.
type H264Reader struct {
stream io.Reader
nalBuffer []byte
@@ -25,7 +25,7 @@ var (
errDataIsNotH264Stream = errors.New("data is not a H264 bitstream")
)
// NewReader creates new H264Reader
// NewReader creates new H264Reader.
func NewReader(in io.Reader) (*H264Reader, error) {
if in == nil {
return nil, errNilReader
@@ -42,7 +42,7 @@ func NewReader(in io.Reader) (*H264Reader, error) {
return reader, nil
}
// NAL H.264 Network Abstraction Layer
// NAL H.264 Network Abstraction Layer.
type NAL struct {
PictureOrderCount uint32
@@ -73,6 +73,7 @@ func (reader *H264Reader) read(numToRead int) (data []byte, e error) {
}
data = reader.readBuffer[0:numShouldRead]
reader.readBuffer = reader.readBuffer[numShouldRead:]
return data, nil
}
@@ -82,7 +83,7 @@ func (reader *H264Reader) bitStreamStartsWithH264Prefix() (prefixLength int, e e
prefixBuffer, e := reader.read(4)
if e != nil {
return
return prefixLength, e
}
n := len(prefixBuffer)
@@ -100,12 +101,14 @@ func (reader *H264Reader) bitStreamStartsWithH264Prefix() (prefixLength int, e e
if nalPrefix3BytesFound {
return 0, io.EOF
}
return 0, errDataIsNotH264Stream
}
// n == 4
if nalPrefix3BytesFound {
reader.nalBuffer = append(reader.nalBuffer, prefixBuffer[3])
return 3, nil
}
@@ -113,6 +116,7 @@ func (reader *H264Reader) bitStreamStartsWithH264Prefix() (prefixLength int, e e
if nalPrefix4BytesFound {
return 4, nil
}
return 0, errDataIsNotH264Stream
}
@@ -147,8 +151,10 @@ func (reader *H264Reader) NextNAL() (*NAL, error) {
nal.parseHeader()
if nal.UnitType == NalUnitTypeSEI {
reader.nalBuffer = nil
continue
}
break
}

View File

@@ -124,14 +124,14 @@ func TestIssue1734_NextNal(t *testing.T) {
}
func TestTrailing01AfterStartCode(t *testing.T) {
r, err := NewReader(bytes.NewReader([]byte{
reader, err := NewReader(bytes.NewReader([]byte{
0x0, 0x0, 0x0, 0x1, 0x01,
0x0, 0x0, 0x0, 0x1, 0x01,
}))
require.NoError(t, err)
for i := 0; i <= 1; i++ {
nal, err := r.NextNAL()
nal, err := reader.NextNAL()
require.NoError(t, err)
require.NotNil(t, nal)
}

View File

@@ -5,10 +5,10 @@ package h264reader
import "strconv"
// NalUnitType is the type of a NAL
// NalUnitType is the type of a NAL.
type NalUnitType uint8
// Enums for NalUnitTypes
// Enums for NalUnitTypes.
const (
NalUnitTypeUnspecified NalUnitType = 0 // Unspecified
NalUnitTypeCodedSliceNonIdr NalUnitType = 1 // Coded slice of a non-IDR picture
@@ -25,12 +25,12 @@ const (
NalUnitTypeFiller NalUnitType = 12 // Filler data
NalUnitTypeSpsExt NalUnitType = 13 // Sequence parameter set extension
NalUnitTypeCodedSliceAux NalUnitType = 19 // Coded slice of an auxiliary coded picture without partitioning
// 14..18 // Reserved
// 20..23 // Reserved
// 24..31 // Unspecified
// 14..18 // Reserved.
// 20..23 // Reserved.
// 24..31 // Unspecified.
)
func (n *NalUnitType) String() string {
func (n *NalUnitType) String() string { //nolint:cyclop
var str string
switch *n {
case NalUnitTypeUnspecified:
@@ -67,5 +67,6 @@ func (n *NalUnitType) String() string {
str = "Unknown"
}
str = str + "(" + strconv.FormatInt(int64(*n), 10) + ")"
return str
}

View File

@@ -27,7 +27,7 @@ type (
}
)
// New builds a new H264 writer
// New builds a new H264 writer.
func New(filename string) (*H264Writer, error) {
f, err := os.Create(filename) //nolint:gosec
if err != nil {
@@ -37,14 +37,14 @@ func New(filename string) (*H264Writer, error) {
return NewWith(f), nil
}
// NewWith initializes a new H264 writer with an io.Writer output
// NewWith initializes a new H264 writer with an io.Writer output.
func NewWith(w io.Writer) *H264Writer {
return &H264Writer{
writer: w,
}
}
// WriteRTP adds a new packet and writes the appropriate headers for it
// WriteRTP adds a new packet and writes the appropriate headers for it.
func (h *H264Writer) WriteRTP(packet *rtp.Packet) error {
if len(packet.Payload) == 0 {
return nil
@@ -71,7 +71,7 @@ func (h *H264Writer) WriteRTP(packet *rtp.Packet) error {
return err
}
// Close closes the underlying writer
// Close closes the underlying writer.
func (h *H264Writer) Close() error {
h.cachedPacket = nil
if h.writer != nil {

View File

@@ -49,7 +49,7 @@ type IVFFrameHeader struct {
Timestamp uint64 // 4-11
}
// IVFReader is used to read IVF files and return frame payloads
// IVFReader is used to read IVF files and return frame payloads.
type IVFReader struct {
stream io.Reader
bytesReadSuccesfully int64
@@ -58,14 +58,14 @@ type IVFReader struct {
}
// NewWith returns a new IVF reader and IVF file header
// with an io.Reader input
func NewWith(in io.Reader) (*IVFReader, *IVFFileHeader, error) {
if in == nil {
// with an io.Reader input.
func NewWith(stream io.Reader) (*IVFReader, *IVFFileHeader, error) {
if stream == nil {
return nil, nil, errNilStream
}
reader := &IVFReader{
stream: in,
stream: stream,
}
header, err := reader.parseFileHeader()
@@ -122,11 +122,12 @@ func (i *IVFReader) ParseNextFrame() ([]byte, *IVFFrameHeader, error) {
}
i.bytesReadSuccesfully += int64(headerBytesRead) + int64(bytesRead)
return payload, header, nil
}
// parseFileHeader reads 32 bytes from stream and returns
// IVF file header. This is always called before ParseNextFrame()
// IVF file header. This is always called before ParseNextFrame().
func (i *IVFReader) parseFileHeader() (*IVFFileHeader, error) {
buffer := make([]byte, ivfFileHeaderSize)
@@ -157,5 +158,6 @@ func (i *IVFReader) parseFileHeader() (*IVFFileHeader, error) {
}
i.bytesReadSuccesfully += int64(bytesRead)
return header, nil
}

View File

@@ -11,7 +11,7 @@ import (
"github.com/stretchr/testify/assert"
)
// buildIVFContainer takes frames and prepends valid IVF file header
// buildIVFContainer takes frames and prepends valid IVF file header.
func buildIVFContainer(frames ...*[]byte) *bytes.Buffer {
// Valid IVF file header taken from: https://github.com/webmproject/...
// vp8-test-vectors/blob/master/vp80-00-comprehensive-001.ivf

View File

@@ -31,7 +31,7 @@ const (
var errInvalidMediaTimebase = errors.New("invalid media timebase")
// IVFWriter is used to take RTP packets and write them to an IVF on disk
// IVFWriter is used to take RTP packets and write them to an IVF on disk.
type IVFWriter struct {
ioWriter io.Writer
count uint64
@@ -51,21 +51,22 @@ type IVFWriter struct {
av1Frame frame.AV1
}
// New builds a new IVF writer
// New builds a new IVF writer.
func New(fileName string, opts ...Option) (*IVFWriter, error) {
f, err := os.Create(fileName) //nolint:gosec
file, err := os.Create(fileName) //nolint:gosec
if err != nil {
return nil, err
}
writer, err := NewWith(f, opts...)
writer, err := NewWith(file, opts...)
if err != nil {
return nil, err
}
writer.ioWriter = f
writer.ioWriter = file
return writer, nil
}
// NewWith initialize a new IVF writer with an io.Writer output
// NewWith initialize a new IVF writer with an io.Writer output.
func NewWith(out io.Writer, opts ...Option) (*IVFWriter, error) {
if out == nil {
return nil, errFileNotOpened
@@ -96,6 +97,7 @@ func NewWith(out io.Writer, opts ...Option) (*IVFWriter, error) {
if writer.timebaseDenominator == 0 {
return nil, errInvalidMediaTimebase
}
return writer, nil
}
@@ -120,6 +122,7 @@ func (i *IVFWriter) writeHeader() error {
binary.LittleEndian.PutUint32(header[28:], 0) // Unused
_, err := i.ioWriter.Write(header)
return err
}
@@ -129,6 +132,7 @@ func (i *IVFWriter) timestampToPts(timestamp uint64) uint64 {
func (i *IVFWriter) writeFrame(frame []byte, timestamp uint64) error {
frameHeader := make([]byte, 12)
//nolint:gosec // G115
binary.LittleEndian.PutUint32(frameHeader[0:], uint32(len(frame))) // Frame length
binary.LittleEndian.PutUint64(frameHeader[4:], i.timestampToPts(timestamp)) // PTS
i.count++
@@ -137,11 +141,12 @@ func (i *IVFWriter) writeFrame(frame []byte, timestamp uint64) error {
return err
}
_, err := i.ioWriter.Write(frame)
return err
}
// WriteRTP adds a new packet and writes the appropriate headers for it
func (i *IVFWriter) WriteRTP(packet *rtp.Packet) error {
// WriteRTP adds a new packet and writes the appropriate headers for it.
func (i *IVFWriter) WriteRTP(packet *rtp.Packet) error { //nolint:cyclop
if i.ioWriter == nil {
return errFileNotOpened
} else if len(packet.Payload) == 0 {
@@ -153,7 +158,7 @@ func (i *IVFWriter) WriteRTP(packet *rtp.Packet) error {
}
relativeTstampMs := 1000 * uint64(packet.Header.Timestamp-i.firstFrameTimestamp) / i.clockRate
if i.isVP8 {
if i.isVP8 { //nolint:nestif
vp8Packet := codecs.VP8Packet{}
if _, err := vp8Packet.Unmarshal(packet.Payload); err != nil {
return err
@@ -201,7 +206,7 @@ func (i *IVFWriter) WriteRTP(packet *rtp.Packet) error {
return nil
}
// Close stops the recording
// Close stops the recording.
func (i *IVFWriter) Close() error {
if i.ioWriter == nil {
// Returns no error as it may be convenient to call
@@ -219,7 +224,7 @@ func (i *IVFWriter) Close() error {
return err
}
buff := make([]byte, 4)
binary.LittleEndian.PutUint32(buff, uint32(i.count))
binary.LittleEndian.PutUint32(buff, uint32(i.count)) //nolint:gosec // G115
if _, err := ws.Write(buff); err != nil {
return err
}
@@ -235,7 +240,7 @@ func (i *IVFWriter) Close() error {
// An Option configures a SampleBuilder.
type Option func(i *IVFWriter) error
// WithCodec configures if IVFWriter is writing AV1 or VP8 packets to disk
// WithCodec configures if IVFWriter is writing AV1 or VP8 packets to disk.
func WithCodec(mimeType string) Option {
return func(i *IVFWriter) error {
if i.isVP8 || i.isAV1 {

View File

@@ -215,15 +215,18 @@ func TestIVFWriter_VP8(t *testing.T) {
// Third test tries to write a valid VP8 packet - No Keyframe
assert.False(addPacketTestCase[0].writer.seenKeyFrame, "Writer's seenKeyFrame should remain false")
assert.Equal(uint64(0), addPacketTestCase[0].writer.count, "Writer's packet count should remain 0")
assert.Equal(nil, addPacketTestCase[0].writer.WriteRTP(midPartPacket), "Write packet failed") // add a mid partition packet
// add a mid partition packet
assert.Equal(nil, addPacketTestCase[0].writer.WriteRTP(midPartPacket), "Write packet failed")
assert.Equal(uint64(0), addPacketTestCase[0].writer.count, "Writer's packet count should remain 0")
// Fifth test tries to write a keyframe packet
assert.True(addPacketTestCase[1].writer.seenKeyFrame, "Writer's seenKeyFrame should now be true")
assert.Equal(uint64(1), addPacketTestCase[1].writer.count, "Writer's packet count should now be 1")
assert.Equal(nil, addPacketTestCase[1].writer.WriteRTP(midPartPacket), "Write packet failed") // add a mid partition packet
// add a mid partition packet
assert.Equal(nil, addPacketTestCase[1].writer.WriteRTP(midPartPacket), "Write packet failed")
assert.Equal(uint64(1), addPacketTestCase[1].writer.count, "Writer's packet count should remain 1")
assert.Equal(nil, addPacketTestCase[1].writer.WriteRTP(validPacket), "Write packet failed") // add a valid packet
// add a valid packet
assert.Equal(nil, addPacketTestCase[1].writer.WriteRTP(validPacket), "Write packet failed")
assert.Equal(uint64(2), addPacketTestCase[1].writer.count, "Writer's packet count should now be 2")
for _, t := range addPacketTestCase {

View File

@@ -10,7 +10,7 @@ import (
"github.com/pion/rtp"
)
// A Sample contains encoded media and timing information
// A Sample contains encoded media and timing information.
type Sample struct {
Data []byte
Timestamp time.Time
@@ -25,7 +25,7 @@ type Sample struct {
}
// Writer defines an interface to handle
// the creation of media files
// the creation of media files.
type Writer interface {
// Add the content of an RTP packet to the media
WriteRTP(packet *rtp.Packet) error

View File

@@ -30,7 +30,7 @@ var (
errChecksumMismatch = errors.New("expected and actual checksum do not match")
)
// OggReader is used to read Ogg files and return page payloads
// OggReader is used to read Ogg files and return page payloads.
type OggReader struct {
stream io.Reader
bytesReadSuccesfully int64
@@ -67,7 +67,7 @@ type OggPageHeader struct {
}
// NewWith returns a new Ogg reader and Ogg header
// with an io.Reader input
// with an io.Reader input.
func NewWith(in io.Reader) (*OggReader, *OggHeader, error) {
return newWith(in /* doChecksum */, true)
}
@@ -126,26 +126,26 @@ func (o *OggReader) readHeaders() (*OggHeader, error) {
// ParseNextPage reads from stream and returns Ogg page payload, header,
// and an error if there is incomplete page data.
func (o *OggReader) ParseNextPage() ([]byte, *OggPageHeader, error) {
h := make([]byte, pageHeaderLen)
func (o *OggReader) ParseNextPage() ([]byte, *OggPageHeader, error) { //nolint:cyclop
header := make([]byte, pageHeaderLen)
n, err := io.ReadFull(o.stream, h)
n, err := io.ReadFull(o.stream, header)
if err != nil {
return nil, nil, err
} else if n < len(h) {
} else if n < len(header) {
return nil, nil, errShortPageHeader
}
pageHeader := &OggPageHeader{
sig: [4]byte{h[0], h[1], h[2], h[3]},
sig: [4]byte{header[0], header[1], header[2], header[3]},
}
pageHeader.version = h[4]
pageHeader.headerType = h[5]
pageHeader.GranulePosition = binary.LittleEndian.Uint64(h[6 : 6+8])
pageHeader.serial = binary.LittleEndian.Uint32(h[14 : 14+4])
pageHeader.index = binary.LittleEndian.Uint32(h[18 : 18+4])
pageHeader.segmentsCount = h[26]
pageHeader.version = header[4]
pageHeader.headerType = header[5]
pageHeader.GranulePosition = binary.LittleEndian.Uint64(header[6 : 6+8])
pageHeader.serial = binary.LittleEndian.Uint32(header[14 : 14+4])
pageHeader.index = binary.LittleEndian.Uint32(header[18 : 18+4])
pageHeader.segmentsCount = header[26]
sizeBuffer := make([]byte, pageHeader.segmentsCount)
if _, err = io.ReadFull(o.stream, sizeBuffer); err != nil {
@@ -168,14 +168,15 @@ func (o *OggReader) ParseNextPage() ([]byte, *OggPageHeader, error) {
checksum = (checksum << 8) ^ o.checksumTable[byte(checksum>>24)^v]
}
for index := range h {
for index := range header {
// Don't include expected checksum in our generation
if index > 21 && index < 26 {
updateChecksum(0)
continue
}
updateChecksum(h[index])
updateChecksum(header[index])
}
for _, s := range sizeBuffer {
updateChecksum(s)
@@ -184,12 +185,12 @@ func (o *OggReader) ParseNextPage() ([]byte, *OggPageHeader, error) {
updateChecksum(payload[index])
}
if binary.LittleEndian.Uint32(h[22:22+4]) != checksum {
if binary.LittleEndian.Uint32(header[22:22+4]) != checksum {
return nil, nil, errChecksumMismatch
}
}
o.bytesReadSuccesfully += int64(len(h) + len(sizeBuffer) + len(payload))
o.bytesReadSuccesfully += int64(len(header) + len(sizeBuffer) + len(payload))
return payload, pageHeader, nil
}
@@ -206,7 +207,7 @@ func generateChecksumTable() *[256]uint32 {
const poly = 0x04c11db7
for i := range table {
r := uint32(i) << 24
r := uint32(i) << 24 //nolint:gosec // G115
for j := 0; j < 8; j++ {
if (r & 0x80000000) != 0 {
r = (r << 1) ^ poly
@@ -216,5 +217,6 @@ func generateChecksumTable() *[256]uint32 {
table[i] = (r & 0xffffffff)
}
}
return &table
}

Some files were not shown because too many files have changed in this diff Show More