mirror of
https://github.com/pion/webrtc.git
synced 2025-09-26 19:21:12 +08:00
Upgrade golangci-lint, more linters
Introduces new linters, upgrade golangci-lint to version (v1.63.4)
This commit is contained in:
@@ -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
24
api.go
@@ -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.
|
||||
|
@@ -27,5 +27,6 @@ func (b *atomicBool) swap(value bool) bool {
|
||||
if value {
|
||||
i = 1
|
||||
}
|
||||
|
||||
return atomic.SwapInt32(&(b.val), i) != 0
|
||||
}
|
||||
|
@@ -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())
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -23,5 +23,6 @@ func (c Configuration) getICEServers() []ICEServer {
|
||||
iceServers[iceServersIndex].URLs[urlsIndex] = rawURL
|
||||
}
|
||||
}
|
||||
|
||||
return iceServers
|
||||
}
|
||||
|
@@ -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)
|
||||
|
21
constants.go
21
constants.go
@@ -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,
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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()
|
||||
|
@@ -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()
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -37,6 +37,7 @@ func TestDTLSRoleFromRemoteSDP(t *testing.T) {
|
||||
if err := parsed.Unmarshal([]byte(raw)); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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
144
errors.go
@@ -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")
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
})
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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{}
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
})
|
||||
|
||||
|
@@ -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)
|
||||
})
|
||||
|
||||
|
@@ -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))
|
||||
})
|
||||
})
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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()))
|
||||
}
|
||||
})
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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())
|
||||
|
@@ -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()
|
||||
}
|
||||
|
||||
|
@@ -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"`
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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())
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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()
|
||||
|
@@ -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
|
||||
|
@@ -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:
|
||||
|
@@ -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):
|
||||
|
@@ -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
|
||||
}
|
||||
|
22
iceserver.go
22
iceserver.go
@@ -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)
|
||||
}
|
||||
|
@@ -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"}`),
|
||||
|
@@ -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)
|
||||
}
|
||||
|
||||
|
@@ -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())
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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})
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
145
mediaengine.go
145
mediaengine.go
@@ -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})
|
||||
}
|
||||
}
|
||||
|
@@ -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)
|
||||
})
|
||||
}
|
||||
|
@@ -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:
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
@@ -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()
|
||||
|
@@ -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))
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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()
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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
Reference in New Issue
Block a user