diff --git a/api_js.go b/api_js.go index fe94bff1fb1..1f0fcb74c2a 100644 --- a/api_js.go +++ b/api_js.go @@ -6,7 +6,7 @@ package webrtc -// API bundles the global funcions of the WebRTC and ORTC API. +// API bundles the global functions of the WebRTC and ORTC API. type API struct { settingEngine *SettingEngine } diff --git a/api_test.go b/api_test.go index 3724fba9c32..06f27697fcc 100644 --- a/api_test.go +++ b/api_test.go @@ -31,12 +31,9 @@ func TestNewAPI(t *testing.T) { func TestNewAPI_Options(t *testing.T) { s := SettingEngine{} s.DetachDataChannels() - m := MediaEngine{} - assert.NoError(t, m.RegisterDefaultCodecs()) api := NewAPI( WithSettingEngine(s), - WithMediaEngine(&m), ) if !api.settingEngine.detach.DataChannels { diff --git a/datachannel.go b/datachannel.go index a846eb58e3d..e975a04f7b5 100644 --- a/datachannel.go +++ b/datachannel.go @@ -236,7 +236,7 @@ func (d *DataChannel) onOpen() { } // OnDial sets an event handler which is invoked when the -// peer has been dialed, but before said peer has responsed +// peer has been dialed, but before said peer has responded func (d *DataChannel) OnDial(f func()) { d.mu.Lock() d.dialHandlerOnce = sync.Once{} @@ -349,18 +349,11 @@ func (d *DataChannel) onError(err error) { } } -// See https://github.com/pion/webrtc/issues/1516 -// nolint:gochecknoglobals -var rlBufPool = sync.Pool{New: func() interface{} { - return make([]byte, dataChannelBufferSize) -}} - func (d *DataChannel) readLoop() { + buffer := make([]byte, dataChannelBufferSize) for { - buffer := rlBufPool.Get().([]byte) //nolint:forcetypeassert n, isString, err := d.dataChannel.ReadDataChannel(buffer) if err != nil { - rlBufPool.Put(buffer) // nolint:staticcheck d.setReadyState(DataChannelStateClosed) if !errors.Is(err, io.EOF) { d.onError(err) @@ -371,8 +364,6 @@ func (d *DataChannel) readLoop() { m := DataChannelMessage{Data: make([]byte, n), IsString: isString} copy(m.Data, buffer[:n]) - // The 'staticcheck' pragma is a false positive on the part of the CI linter. - rlBufPool.Put(buffer) // nolint:staticcheck // NB: Why was DataChannelMessage not passed as a pointer value? d.onMessage(m) // nolint:staticcheck diff --git a/datachannel_js.go b/datachannel_js.go index a6d1ae5e8aa..5e5fc4b0cda 100644 --- a/datachannel_js.go +++ b/datachannel_js.go @@ -115,7 +115,7 @@ func (d *DataChannel) SendText(s string) (err error) { // Before calling Detach you have to enable this behavior by calling // webrtc.DetachDataChannels(). Combining detached and normal data channels // is not supported. -// Please reffer to the data-channels-detach example and the +// Please refer to the data-channels-detach example and the // pion/datachannel documentation for the correct way to handle the // resulting DataChannel object. func (d *DataChannel) Detach() (datachannel.ReadWriteCloser, error) { diff --git a/dtlstransport.go b/dtlstransport.go index 410d1645831..ec08a0846d3 100644 --- a/dtlstransport.go +++ b/dtlstransport.go @@ -377,6 +377,8 @@ func (t *DTLSTransport) Start(remoteParameters DTLSParameters) error { t.srtpProtectionProfile = srtp.ProtectionProfileAeadAes256Gcm case dtls.SRTP_AES128_CM_HMAC_SHA1_80: t.srtpProtectionProfile = srtp.ProtectionProfileAes128CmHmacSha1_80 + case dtls.SRTP_NULL_HMAC_SHA1_80: + t.srtpProtectionProfile = srtp.ProtectionProfileNullHmacSha1_80 default: t.onStateChange(DTLSTransportStateFailed) return ErrNoSRTPProtectionProfile diff --git a/examples/bandwidth-estimation-from-disk/main.go b/examples/bandwidth-estimation-from-disk/main.go index c1cf585f87c..54e73d1a00c 100644 --- a/examples/bandwidth-estimation-from-disk/main.go +++ b/examples/bandwidth-estimation-from-disk/main.go @@ -8,17 +8,20 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" "errors" "fmt" "io" "os" + "strings" "time" "github.com/pion/interceptor" "github.com/pion/interceptor/pkg/cc" "github.com/pion/interceptor/pkg/gcc" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" "github.com/pion/webrtc/v4/pkg/media" "github.com/pion/webrtc/v4/pkg/media/ivfreader" ) @@ -144,7 +147,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription if err = peerConnection.SetRemoteDescription(offer); err != nil { @@ -171,7 +174,7 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Open a IVF file and start reading using our IVFReader file, err := os.Open(qualityLevels[currentQuality].fileName) @@ -254,3 +257,45 @@ func setReaderFile(filename string) func(_ int64) io.Reader { return file } } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/broadcast/main.go b/examples/broadcast/main.go index 22a3d914ddc..47eb7f2df9f 100644 --- a/examples/broadcast/main.go +++ b/examples/broadcast/main.go @@ -8,26 +8,29 @@ package main import ( + "encoding/base64" + "encoding/json" "errors" "flag" "fmt" "io" + "net/http" + "strconv" "github.com/pion/interceptor" "github.com/pion/interceptor/pkg/intervalpli" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) func main() { // nolint:gocognit port := flag.Int("port", 8080, "http server port") flag.Parse() - sdpChan := signal.HTTPSDPServer(*port) + sdpChan := httpSDPServer(*port) // Everything below is the Pion WebRTC API, thanks for using it ❤️. offer := webrtc.SessionDescription{} - signal.Decode(<-sdpChan, &offer) + decode(<-sdpChan, &offer) fmt.Println("") peerConnectionConfig := webrtc.Configuration{ @@ -132,7 +135,7 @@ func main() { // nolint:gocognit <-gatherComplete // Get the LocalDescription and take it to base64 so we can paste in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) localTrack := <-localTrackChan for { @@ -140,7 +143,7 @@ func main() { // nolint:gocognit fmt.Println("Curl an base64 SDP to start sendonly peer connection") recvOnlyOffer := webrtc.SessionDescription{} - signal.Decode(<-sdpChan, &recvOnlyOffer) + decode(<-sdpChan, &recvOnlyOffer) // Create a new PeerConnection peerConnection, err := webrtc.NewPeerConnection(peerConnectionConfig) @@ -192,6 +195,45 @@ func main() { // nolint:gocognit <-gatherComplete // Get the LocalDescription and take it to base64 so we can paste in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) } } + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} + +// 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 + sdpChan <- string(body) + }) + + go func() { + // nolint: gosec + panic(http.ListenAndServe(":"+strconv.Itoa(port), nil)) + }() + + return sdpChan +} diff --git a/examples/data-channels-detach/jsfiddle/main.go b/examples/data-channels-detach/jsfiddle/main.go index bb093cf925b..6ed1626a899 100644 --- a/examples/data-channels-detach/jsfiddle/main.go +++ b/examples/data-channels-detach/jsfiddle/main.go @@ -7,14 +7,19 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" + "errors" "fmt" "io" + "os" + "strings" "syscall/js" "time" + "github.com/pion/randutil" "github.com/pion/webrtc/v4" - - "github.com/pion/webrtc/v4/examples/internal/signal" ) const messageSize = 15 @@ -95,7 +100,7 @@ func main() { }) peerConnection.OnICECandidate(func(candidate *webrtc.ICECandidate) { if candidate != nil { - encodedDescr := signal.Encode(peerConnection.LocalDescription()) + encodedDescr := encode(peerConnection.LocalDescription()) el := getElementByID("localSessionDescription") el.Set("value", encodedDescr) } @@ -126,7 +131,7 @@ func main() { } descr := webrtc.SessionDescription{} - signal.Decode(sd, &descr) + decode(sd, &descr) if err := peerConnection.SetRemoteDescription(descr); err != nil { handleError(err) } @@ -155,13 +160,15 @@ func ReadLoop(d io.Reader) { // WriteLoop shows how to write to the datachannel directly func WriteLoop(d io.Writer) { for range time.NewTicker(5 * time.Second).C { - message := signal.RandSeq(messageSize) - log(fmt.Sprintf("Sending %s \n", message)) - - _, err := d.Write([]byte(message)) + message, err := randutil.GenerateCryptoRandomString(messageSize, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") if err != nil { handleError(err) } + + log(fmt.Sprintf("Sending %s \n", message)) + if _, err := d.Write([]byte(message)); err != nil { + handleError(err) + } } } @@ -178,3 +185,45 @@ func handleError(err error) { func getElementByID(id string) js.Value { return js.Global().Get("document").Call("getElementById", id) } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/data-channels-detach/main.go b/examples/data-channels-detach/main.go index 8e16245119e..4a4ef2b3b7e 100644 --- a/examples/data-channels-detach/main.go +++ b/examples/data-channels-detach/main.go @@ -5,13 +5,18 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" + "errors" "fmt" "io" "os" + "strings" "time" + "github.com/pion/randutil" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) const messageSize = 15 @@ -94,7 +99,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription err = peerConnection.SetRemoteDescription(offer) @@ -123,7 +128,7 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} @@ -146,12 +151,56 @@ func ReadLoop(d io.Reader) { // WriteLoop shows how to write to the datachannel directly func WriteLoop(d io.Writer) { for range time.NewTicker(5 * time.Second).C { - message := signal.RandSeq(messageSize) + message, err := randutil.GenerateCryptoRandomString(messageSize, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + if err != nil { + panic(err) + } + fmt.Printf("Sending %s \n", message) + if _, err := d.Write([]byte(message)); err != nil { + panic(err) + } + } +} - _, err := d.Write([]byte(message)) - if err != nil { +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { panic(err) } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) } } diff --git a/examples/data-channels/jsfiddle/main.go b/examples/data-channels/jsfiddle/main.go index 001b3f2226f..44faed1a593 100644 --- a/examples/data-channels/jsfiddle/main.go +++ b/examples/data-channels/jsfiddle/main.go @@ -7,11 +7,17 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" + "errors" "fmt" + "io" + "os" + "strings" "syscall/js" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) func main() { @@ -63,7 +69,7 @@ func main() { }) pc.OnICECandidate(func(candidate *webrtc.ICECandidate) { if candidate != nil { - encodedDescr := signal.Encode(pc.LocalDescription()) + encodedDescr := encode(pc.LocalDescription()) el := getElementByID("localSessionDescription") el.Set("value", encodedDescr) } @@ -94,7 +100,7 @@ func main() { } descr := webrtc.SessionDescription{} - signal.Decode(sd, &descr) + decode(sd, &descr) if err := pc.SetRemoteDescription(descr); err != nil { handleError(err) } @@ -146,3 +152,45 @@ func handleError(err error) { func getElementByID(id string) js.Value { return js.Global().Get("document").Call("getElementById", id) } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/data-channels/main.go b/examples/data-channels/main.go index 012413f241b..217f59f9dbc 100644 --- a/examples/data-channels/main.go +++ b/examples/data-channels/main.go @@ -5,12 +5,18 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" + "errors" "fmt" + "io" "os" + "strings" "time" + "github.com/pion/randutil" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) func main() { @@ -65,12 +71,14 @@ func main() { 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()) for range time.NewTicker(5 * time.Second).C { - message := signal.RandSeq(15) - fmt.Printf("Sending '%s'\n", message) + message, sendErr := randutil.GenerateCryptoRandomString(15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + if sendErr != nil { + panic(sendErr) + } // Send the message as text - sendErr := d.SendText(message) - if sendErr != nil { + fmt.Printf("Sending '%s'\n", message) + if sendErr = d.SendText(message); sendErr != nil { panic(sendErr) } } @@ -84,7 +92,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription err = peerConnection.SetRemoteDescription(offer) @@ -113,8 +121,50 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/insertable-streams/README.md b/examples/insertable-streams/README.md index 73a77bc9761..09d23d3b928 100644 --- a/examples/insertable-streams/README.md +++ b/examples/insertable-streams/README.md @@ -4,7 +4,7 @@ This example modifies the video with a single-byte XOR cipher before sending, an decrypts in Javascript. insertable-streams allows the browser to process encoded video. You could implement -E2E encyption, add metadata or insert a completely different video feed! +E2E encryption, add metadata or insert a completely different video feed! ## Instructions ### Create IVF named `output.ivf` that contains a VP8 track diff --git a/examples/insertable-streams/main.go b/examples/insertable-streams/main.go index b80229b2645..97d3eb2f17f 100644 --- a/examples/insertable-streams/main.go +++ b/examples/insertable-streams/main.go @@ -8,15 +8,18 @@ package main import ( + "bufio" "context" + "encoding/base64" + "encoding/json" "errors" "fmt" "io" "os" + "strings" "time" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" "github.com/pion/webrtc/v4/pkg/media" "github.com/pion/webrtc/v4/pkg/media/ivfreader" ) @@ -136,7 +139,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription if err = peerConnection.SetRemoteDescription(offer); err != nil { @@ -163,8 +166,50 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/internal/signal/http.go b/examples/internal/signal/http.go deleted file mode 100644 index d705e6428f2..00000000000 --- a/examples/internal/signal/http.go +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package signal - -import ( - "fmt" - "io" - "net/http" - "strconv" -) - -// 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") - sdpChan <- string(body) - }) - - go func() { - // nolint: gosec - err := http.ListenAndServe(":"+strconv.Itoa(port), nil) - if err != nil { - panic(err) - } - }() - - return sdpChan -} diff --git a/examples/internal/signal/rand.go b/examples/internal/signal/rand.go deleted file mode 100644 index c55fc6cf54f..00000000000 --- a/examples/internal/signal/rand.go +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package signal - -import "github.com/pion/randutil" - -// RandSeq generates a random string to serve as dummy data -// -// It returns a deterministic sequence of values each time a program is run. -// Use rand.Seed() function in your real applications. -func RandSeq(n int) string { - val, err := randutil.GenerateCryptoRandomString(n, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - if err != nil { - panic(err) - } - - return val -} diff --git a/examples/internal/signal/signal.go b/examples/internal/signal/signal.go deleted file mode 100644 index 14547a33c0d..00000000000 --- a/examples/internal/signal/signal.go +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -// Package signal contains helpers to exchange the SDP session -// description between examples. -package signal - -import ( - "bufio" - "bytes" - "compress/gzip" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "os" - "strings" -) - -// Allows compressing offer/answer to bypass terminal input limits. -const compress = false - -// MustReadStdin blocks until input is received from stdin -func MustReadStdin() string { - r := bufio.NewReader(os.Stdin) - - var in string - for { - var err error - in, err = r.ReadString('\n') - if err != io.EOF { - if err != nil { - panic(err) - } - } - in = strings.TrimSpace(in) - if len(in) > 0 { - break - } - } - - fmt.Println("") - - return in -} - -// Encode encodes the input in base64 -// It can optionally zip the input before encoding -func Encode(obj interface{}) string { - b, err := json.Marshal(obj) - if err != nil { - panic(err) - } - - if compress { - b = zip(b) - } - - return base64.StdEncoding.EncodeToString(b) -} - -// Decode decodes the input from base64 -// It can optionally unzip the input after decoding -func Decode(in string, obj interface{}) { - b, err := base64.StdEncoding.DecodeString(in) - if err != nil { - panic(err) - } - - if compress { - b = unzip(b) - } - - err = json.Unmarshal(b, obj) - if err != nil { - panic(err) - } -} - -func zip(in []byte) []byte { - var b bytes.Buffer - gz := gzip.NewWriter(&b) - _, err := gz.Write(in) - if err != nil { - panic(err) - } - err = gz.Flush() - if err != nil { - panic(err) - } - err = gz.Close() - if err != nil { - panic(err) - } - return b.Bytes() -} - -func unzip(in []byte) []byte { - var b bytes.Buffer - _, err := b.Write(in) - if err != nil { - panic(err) - } - r, err := gzip.NewReader(&b) - if err != nil { - panic(err) - } - res, err := io.ReadAll(r) - if err != nil { - panic(err) - } - return res -} diff --git a/examples/ortc-media/main.go b/examples/ortc-media/main.go index 39155024210..f601280019e 100644 --- a/examples/ortc-media/main.go +++ b/examples/ortc-media/main.go @@ -8,15 +8,20 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" "errors" "flag" "fmt" "io" + "net/http" "os" + "strconv" + "strings" "time" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" "github.com/pion/webrtc/v4/pkg/media" "github.com/pion/webrtc/v4/pkg/media/ivfreader" ) @@ -142,16 +147,16 @@ func main() { iceRole := webrtc.ICERoleControlled // Exchange the information - fmt.Println(signal.Encode(s)) + fmt.Println(encode(&s)) remoteSignal := Signal{} if *isOffer { - signalingChan := signal.HTTPSDPServer(*port) - signal.Decode(<-signalingChan, &remoteSignal) + signalingChan := httpSDPServer(*port) + decode(<-signalingChan, &remoteSignal) iceRole = webrtc.ICERoleControlling } else { - signal.Decode(signal.MustReadStdin(), &remoteSignal) + decode(readUntilNewline(), &remoteSignal) } if err = ice.SetRemoteCandidates(remoteSignal.ICECandidates); err != nil { @@ -244,3 +249,62 @@ type Signal struct { DTLSParameters webrtc.DTLSParameters `json:"dtlsParameters"` RTPSendParameters webrtc.RTPSendParameters `json:"rtpSendParameters"` } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *Signal) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// Decode a base64 and unmarshal JSON into a SessionDescription +func decode(in string, obj *Signal) { + b, err := base64.StdEncoding.DecodeString(in) + if err != nil { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} + +// 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 + sdpChan <- string(body) + }) + + go func() { + // nolint: gosec + panic(http.ListenAndServe(":"+strconv.Itoa(port), nil)) + }() + + return sdpChan +} diff --git a/examples/ortc/main.go b/examples/ortc/main.go index 60ad6c83f3c..70098d0fad2 100644 --- a/examples/ortc/main.go +++ b/examples/ortc/main.go @@ -8,12 +8,21 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" + "errors" "flag" "fmt" + "io" + "net/http" + "os" + "strconv" + "strings" "time" + "github.com/pion/randutil" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) func main() { @@ -103,16 +112,16 @@ func main() { iceRole := webrtc.ICERoleControlled // Exchange the information - fmt.Println(signal.Encode(s)) + fmt.Println(encode(s)) remoteSignal := Signal{} if *isOffer { - signalingChan := signal.HTTPSDPServer(*port) - signal.Decode(<-signalingChan, &remoteSignal) + signalingChan := httpSDPServer(*port) + decode(<-signalingChan, &remoteSignal) iceRole = webrtc.ICERoleControlling } else { - signal.Decode(signal.MustReadStdin(), &remoteSignal) + decode(readUntilNewline(), &remoteSignal) } if err = ice.SetRemoteCandidates(remoteSignal.ICECandidates); err != nil { @@ -175,12 +184,74 @@ func handleOnOpen(channel *webrtc.DataChannel) 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()) for range time.NewTicker(5 * time.Second).C { - message := signal.RandSeq(15) - fmt.Printf("Sending '%s' \n", message) + message, err := randutil.GenerateCryptoRandomString(15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + if err != nil { + panic(err) + } + fmt.Printf("Sending %s \n", message) if err := channel.SendText(message); err != nil { panic(err) } } } } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj Signal) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// Decode a base64 and unmarshal JSON into a SessionDescription +func decode(in string, obj *Signal) { + b, err := base64.StdEncoding.DecodeString(in) + if err != nil { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} + +// 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 + sdpChan <- string(body) + }) + + go func() { + // nolint: gosec + panic(http.ListenAndServe(":"+strconv.Itoa(port), nil)) + }() + + return sdpChan +} diff --git a/examples/pion-to-pion/answer/main.go b/examples/pion-to-pion/answer/main.go index 97a53f58268..dd4dd7ab11e 100644 --- a/examples/pion-to-pion/answer/main.go +++ b/examples/pion-to-pion/answer/main.go @@ -15,8 +15,8 @@ import ( "sync" "time" + "github.com/pion/randutil" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) func signalCandidate(addr string, c *webrtc.ICECandidate) error { @@ -164,12 +164,14 @@ func main() { // nolint:gocognit 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()) for range time.NewTicker(5 * time.Second).C { - message := signal.RandSeq(15) - fmt.Printf("Sending '%s'\n", message) + message, sendTextErr := randutil.GenerateCryptoRandomString(15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + if sendTextErr != nil { + panic(sendTextErr) + } // Send the message as text - sendTextErr := d.SendText(message) - if sendTextErr != nil { + fmt.Printf("Sending '%s'\n", message) + if sendTextErr = d.SendText(message); sendTextErr != nil { panic(sendTextErr) } } diff --git a/examples/pion-to-pion/offer/main.go b/examples/pion-to-pion/offer/main.go index 236212a74a9..16332f0d235 100644 --- a/examples/pion-to-pion/offer/main.go +++ b/examples/pion-to-pion/offer/main.go @@ -15,8 +15,8 @@ import ( "sync" "time" + "github.com/pion/randutil" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) func signalCandidate(addr string, c *webrtc.ICECandidate) error { @@ -145,12 +145,14 @@ func main() { //nolint:gocognit 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()) for range time.NewTicker(5 * time.Second).C { - message := signal.RandSeq(15) - fmt.Printf("Sending '%s'\n", message) + message, sendTextErr := randutil.GenerateCryptoRandomString(15, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + if sendTextErr != nil { + panic(sendTextErr) + } // Send the message as text - sendTextErr := dataChannel.SendText(message) - if sendTextErr != nil { + fmt.Printf("Sending '%s'\n", message) + if sendTextErr = dataChannel.SendText(message); sendTextErr != nil { panic(sendTextErr) } } diff --git a/examples/play-from-disk/main.go b/examples/play-from-disk/main.go index 13b7f707cfa..c6bb33632ff 100644 --- a/examples/play-from-disk/main.go +++ b/examples/play-from-disk/main.go @@ -8,15 +8,18 @@ package main import ( + "bufio" "context" + "encoding/base64" + "encoding/json" "errors" "fmt" "io" "os" + "strings" "time" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" "github.com/pion/webrtc/v4/pkg/media" "github.com/pion/webrtc/v4/pkg/media/ivfreader" "github.com/pion/webrtc/v4/pkg/media/oggreader" @@ -248,7 +251,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription if err = peerConnection.SetRemoteDescription(offer); err != nil { @@ -275,8 +278,50 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/reflect/main.go b/examples/reflect/main.go index ff859432f90..f666cf17e37 100644 --- a/examples/reflect/main.go +++ b/examples/reflect/main.go @@ -8,13 +8,18 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" + "errors" "fmt" + "io" "os" + "strings" "github.com/pion/interceptor" "github.com/pion/interceptor/pkg/intervalpli" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) // nolint:gocognit @@ -102,7 +107,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription err = peerConnection.SetRemoteDescription(offer) @@ -167,8 +172,50 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/rtcp-processing/main.go b/examples/rtcp-processing/main.go index afd01870d1e..5b5e61b2f62 100644 --- a/examples/rtcp-processing/main.go +++ b/examples/rtcp-processing/main.go @@ -8,10 +8,16 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" + "errors" "fmt" + "io" + "os" + "strings" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) func main() { @@ -60,7 +66,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription err = peerConnection.SetRemoteDescription(offer) @@ -89,8 +95,50 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/rtp-forwarder/main.go b/examples/rtp-forwarder/main.go index 57d02816f5c..12cd65d1f91 100644 --- a/examples/rtp-forwarder/main.go +++ b/examples/rtp-forwarder/main.go @@ -8,16 +8,20 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" "errors" "fmt" + "io" "net" "os" + "strings" "github.com/pion/interceptor" "github.com/pion/interceptor/pkg/intervalpli" "github.com/pion/rtp" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) type udpConn struct { @@ -207,7 +211,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription if err = peerConnection.SetRemoteDescription(offer); err != nil { @@ -234,8 +238,50 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/rtp-to-webrtc/main.go b/examples/rtp-to-webrtc/main.go index 81acf82c811..5b86f9acadb 100644 --- a/examples/rtp-to-webrtc/main.go +++ b/examples/rtp-to-webrtc/main.go @@ -8,13 +8,17 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" "errors" "fmt" "io" "net" + "os" + "strings" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) func main() { @@ -85,7 +89,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription if err = peerConnection.SetRemoteDescription(offer); err != nil { @@ -112,7 +116,7 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Read RTP packets forever and send them to the WebRTC Client inboundRTPPacket := make([]byte, 1600) // UDP MTU @@ -132,3 +136,45 @@ func main() { } } } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/save-to-disk-av1/main.go b/examples/save-to-disk-av1/main.go index 29f03e59d7c..a985f637801 100644 --- a/examples/save-to-disk-av1/main.go +++ b/examples/save-to-disk-av1/main.go @@ -8,14 +8,18 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" + "errors" "fmt" + "io" "os" "strings" "github.com/pion/interceptor" "github.com/pion/interceptor/pkg/intervalpli" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" "github.com/pion/webrtc/v4/pkg/media" "github.com/pion/webrtc/v4/pkg/media/ivfwriter" ) @@ -133,7 +137,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription err = peerConnection.SetRemoteDescription(offer) @@ -162,8 +166,50 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/save-to-disk/main.go b/examples/save-to-disk/main.go index 1113f422081..a519aed2a28 100644 --- a/examples/save-to-disk/main.go +++ b/examples/save-to-disk/main.go @@ -8,14 +8,18 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" + "errors" "fmt" + "io" "os" "strings" "github.com/pion/interceptor" "github.com/pion/interceptor/pkg/intervalpli" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" "github.com/pion/webrtc/v4/pkg/media" "github.com/pion/webrtc/v4/pkg/media/ivfwriter" "github.com/pion/webrtc/v4/pkg/media/oggwriter" @@ -161,7 +165,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription err = peerConnection.SetRemoteDescription(offer) @@ -190,8 +194,50 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/simulcast/main.go b/examples/simulcast/main.go index ded6bdfe07d..25b06928906 100644 --- a/examples/simulcast/main.go +++ b/examples/simulcast/main.go @@ -8,15 +8,18 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" "errors" "fmt" "io" "os" + "strings" "time" "github.com/pion/rtcp" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) // nolint:gocognit @@ -64,14 +67,18 @@ func main() { } outputTracks["f"] = outputTrack - // Add this newly created track to the PeerConnection - if _, err = peerConnection.AddTrack(outputTracks["q"]); err != nil { + if _, err = peerConnection.AddTransceiverFromKind(webrtc.RTPCodecTypeVideo, webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionRecvonly}); err != nil { panic(err) } - if _, err = peerConnection.AddTrack(outputTracks["h"]); err != nil { + + // 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 { + panic(err) + } + if _, err = peerConnection.AddTransceiverFromTrack(outputTracks["h"], webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}); err != nil { panic(err) } - if _, err = peerConnection.AddTrack(outputTracks["f"]); err != nil { + if _, err = peerConnection.AddTransceiverFromTrack(outputTracks["f"], webrtc.RTPTransceiverInit{Direction: webrtc.RTPTransceiverDirectionSendonly}); err != nil { panic(err) } @@ -92,7 +99,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) if err = peerConnection.SetRemoteDescription(offer); err != nil { panic(err) @@ -167,8 +174,50 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/stats/main.go b/examples/stats/main.go index 9d3a9574f8e..1cf7082eba3 100644 --- a/examples/stats/main.go +++ b/examples/stats/main.go @@ -8,13 +8,19 @@ package main import ( + "bufio" + "encoding/base64" + "encoding/json" + "errors" "fmt" + "io" + "os" + "strings" "time" "github.com/pion/interceptor" "github.com/pion/interceptor/pkg/stats" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) // nolint:gocognit @@ -108,7 +114,7 @@ func main() { // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription err = peerConnection.SetRemoteDescription(offer) @@ -137,8 +143,50 @@ func main() { <-gatherComplete // Output the answer in base64 so we can paste it in browser - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Block forever select {} } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/swap-tracks/main.go b/examples/swap-tracks/main.go index 85935baa8fb..8b36b6120ce 100644 --- a/examples/swap-tracks/main.go +++ b/examples/swap-tracks/main.go @@ -8,16 +8,20 @@ package main import ( + "bufio" "context" + "encoding/base64" + "encoding/json" "errors" "fmt" "io" + "os" + "strings" "time" "github.com/pion/rtcp" "github.com/pion/rtp" "github.com/pion/webrtc/v4" - "github.com/pion/webrtc/v4/examples/internal/signal" ) func main() { // nolint:gocognit @@ -68,7 +72,7 @@ func main() { // nolint:gocognit // Wait for the offer to be pasted offer := webrtc.SessionDescription{} - signal.Decode(signal.MustReadStdin(), &offer) + decode(readUntilNewline(), &offer) // Set the remote SessionDescription err = peerConnection.SetRemoteDescription(offer) @@ -166,7 +170,7 @@ func main() { // nolint:gocognit // in a production application you should exchange ICE Candidates via OnICECandidate <-gatherComplete - fmt.Println(signal.Encode(*peerConnection.LocalDescription())) + fmt.Println(encode(peerConnection.LocalDescription())) // Asynchronously take all packets in the channel and write them out to our // track @@ -215,3 +219,45 @@ func main() { // nolint:gocognit fmt.Printf("Switched to track #%v\n", currTrack+1) } } + +// Read from stdin until we get a newline +func readUntilNewline() (in string) { + var err error + + r := bufio.NewReader(os.Stdin) + for { + in, err = r.ReadString('\n') + if err != nil && !errors.Is(err, io.EOF) { + panic(err) + } + + if in = strings.TrimSpace(in); len(in) > 0 { + break + } + } + + fmt.Println("") + return +} + +// JSON encode + base64 a SessionDescription +func encode(obj *webrtc.SessionDescription) string { + b, err := json.Marshal(obj) + if err != nil { + panic(err) + } + + return base64.StdEncoding.EncodeToString(b) +} + +// 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 { + panic(err) + } + + if err = json.Unmarshal(b, obj); err != nil { + panic(err) + } +} diff --git a/examples/whip-whep/main.go b/examples/whip-whep/main.go index b2cc83a591f..f5676e61680 100644 --- a/examples/whip-whep/main.go +++ b/examples/whip-whep/main.go @@ -193,5 +193,5 @@ func writeAnswer(w http.ResponseWriter, peerConnection *webrtc.PeerConnection, o w.WriteHeader(http.StatusCreated) // Write Answer with Candidates as HTTP Response - fmt.Fprint(w, peerConnection.LocalDescription().SDP) + fmt.Fprint(w, peerConnection.LocalDescription().SDP) //nolint: errcheck } diff --git a/go.mod b/go.mod index ca23337eccb..78f23a03edf 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,22 @@ module github.com/pion/webrtc/v4 go 1.19 require ( - github.com/pion/datachannel v1.5.6 - github.com/pion/dtls/v2 v2.2.10 - github.com/pion/ice/v3 v3.0.6 + github.com/pion/datachannel v1.5.8 + github.com/pion/dtls/v2 v2.2.12 + github.com/pion/ice/v3 v3.0.10 github.com/pion/interceptor v0.1.29 github.com/pion/logging v0.2.2 github.com/pion/randutil v0.1.0 github.com/pion/rtcp v1.2.14 - github.com/pion/rtp v1.8.5 - github.com/pion/sctp v1.8.16 + github.com/pion/rtp v1.8.7 + github.com/pion/sctp v1.8.19 github.com/pion/sdp/v3 v3.0.9 - github.com/pion/srtp/v3 v3.0.1 + github.com/pion/srtp/v3 v3.0.3 github.com/pion/stun/v2 v2.0.0 github.com/pion/transport/v3 v3.0.2 github.com/sclevine/agouti v3.0.0+incompatible github.com/stretchr/testify v1.9.0 - golang.org/x/net v0.22.0 + golang.org/x/net v0.27.0 ) require ( @@ -27,10 +27,10 @@ require ( github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.17.0 // indirect github.com/pion/mdns/v2 v2.0.7 // indirect - github.com/pion/transport/v2 v2.2.4 // indirect - github.com/pion/turn/v3 v3.0.2 // indirect + github.com/pion/transport/v2 v2.2.5 // indirect + github.com/pion/turn/v3 v3.0.3 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/crypto v0.21.0 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/sys v0.22.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fc25c58f65a..cd49bd111d8 100644 --- a/go.sum +++ b/go.sum @@ -22,10 +22,7 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -38,13 +35,13 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= -github.com/pion/datachannel v1.5.6 h1:1IxKJntfSlYkpUj8LlYRSWpYiTTC02nUrOE8T3DqGeg= -github.com/pion/datachannel v1.5.6/go.mod h1:1eKT6Q85pRnr2mHiWHxJwO50SfZRtWHTsNIVb/NfGW4= +github.com/pion/datachannel v1.5.8 h1:ph1P1NsGkazkjrvyMfhRBUAWMxugJjq2HfQifaOoSNo= +github.com/pion/datachannel v1.5.8/go.mod h1:PgmdpoaNBLX9HNzNClmdki4DYW5JtI7Yibu8QzbL3tI= github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= -github.com/pion/dtls/v2 v2.2.10 h1:u2Axk+FyIR1VFTPurktB+1zoEPGIW3bmyj3LEFrXjAA= -github.com/pion/dtls/v2 v2.2.10/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= -github.com/pion/ice/v3 v3.0.6 h1:UC5vZCMhmve7yv+Y6E5eTnRTl+t9LLtmeBYQ9038Zm8= -github.com/pion/ice/v3 v3.0.6/go.mod h1:4eMTUKQEjC1fGQGB6qUzy2ux9Pc1v9EsO3hNaii+kXI= +github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk= +github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE= +github.com/pion/ice/v3 v3.0.10 h1:ZRNZkZW4C/CFniAfEH7Ix4hW+TVGENsNkzebYY72TYY= +github.com/pion/ice/v3 v3.0.10/go.mod h1:aSHw7r4guKNumxc5dCWfzWga+qtz1ki/nnpHfJTSLTs= github.com/pion/interceptor v0.1.29 h1:39fsnlP1U8gw2JzOFWdfCU82vHvhW9o0rZnZF56wF+M= github.com/pion/interceptor v0.1.29/go.mod h1:ri+LGNjRUc5xUNtDEPzfdkmSqISixVTBF/z/Zms/6T4= github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= @@ -53,29 +50,27 @@ github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= -github.com/pion/rtcp v1.2.12/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= github.com/pion/rtcp v1.2.14 h1:KCkGV3vJ+4DAJmvP0vaQShsb0xkRfWkO540Gy102KyE= github.com/pion/rtcp v1.2.14/go.mod h1:sn6qjxvnwyAkkPzPULIbVqSKI5Dv54Rv7VG0kNxh9L4= -github.com/pion/rtp v1.8.3/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= -github.com/pion/rtp v1.8.5 h1:uYzINfaK+9yWs7r537z/Rc1SvT8ILjBcmDOpJcTB+OU= -github.com/pion/rtp v1.8.5/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= -github.com/pion/sctp v1.8.13/go.mod h1:YKSgO/bO/6aOMP9LCie1DuD7m+GamiK2yIiPM6vH+GA= -github.com/pion/sctp v1.8.16 h1:PKrMs+o9EMLRvFfXq59WFsC+V8mN1wnKzqrv+3D/gYY= -github.com/pion/sctp v1.8.16/go.mod h1:P6PbDVA++OJMrVNg2AL3XtYHV4uD6dvfyOovCgMs0PE= +github.com/pion/rtp v1.8.7 h1:qslKkG8qxvQ7hqaxkmL7Pl0XcUm+/Er7nMnu6Vq+ZxM= +github.com/pion/rtp v1.8.7/go.mod h1:pBGHaFt/yW7bf1jjWAoUjpSNoDnw98KTMg+jWWvziqU= +github.com/pion/sctp v1.8.19 h1:2CYuw+SQ5vkQ9t0HdOPccsCz1GQMDuVy5PglLgKVBW8= +github.com/pion/sctp v1.8.19/go.mod h1:P6PbDVA++OJMrVNg2AL3XtYHV4uD6dvfyOovCgMs0PE= github.com/pion/sdp/v3 v3.0.9 h1:pX++dCHoHUwq43kuwf3PyJfHlwIj4hXA7Vrifiq0IJY= github.com/pion/sdp/v3 v3.0.9/go.mod h1:B5xmvENq5IXJimIO4zfp6LAe1fD9N+kFv+V/1lOdz8M= -github.com/pion/srtp/v3 v3.0.1 h1:AkIQRIZ+3tAOJMQ7G301xtrD1vekQbNeRO7eY1K8ZHk= -github.com/pion/srtp/v3 v3.0.1/go.mod h1:3R3a1qIOIxBkVTLGFjafKK6/fJoTdQDhcC67HOyMbJ8= +github.com/pion/srtp/v3 v3.0.3 h1:tRtEOpmR8NtsB/KndlKXFOj/AIIs6aPrCq4TlAatC4M= +github.com/pion/srtp/v3 v3.0.3/go.mod h1:Bp9ztzPCoE0ETca/R+bTVTO5kBgaQMiQkTmZWwazDTc= github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= -github.com/pion/transport/v2 v2.2.4 h1:41JJK6DZQYSeVLxILA2+F4ZkKb4Xd/tFJZRFZQ9QAlo= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= +github.com/pion/transport/v2 v2.2.5 h1:iyi25i/21gQck4hfRhomF6SktmUQjRsRW4WJdhfc3Kc= +github.com/pion/transport/v2 v2.2.5/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= github.com/pion/transport/v3 v3.0.2 h1:r+40RJR25S9w3jbA6/5uEPTzcdn7ncyU44RWCbHkLg4= github.com/pion/transport/v3 v3.0.2/go.mod h1:nIToODoOlb5If2jF9y2Igfx3PFYWfuXi37m0IlWa/D0= -github.com/pion/turn/v3 v3.0.2 h1:iBonAIIKRwkVUJBFiFd/kSjytP7FlX0HwCyBDJPRDdU= -github.com/pion/turn/v3 v3.0.2/go.mod h1:vw0Dz420q7VYAF3J4wJKzReLHIo2LGp4ev8nXQexYsc= +github.com/pion/turn/v3 v3.0.3 h1:1e3GVk8gHZLPBA5LqadWYV60lmaKUaHCkm9DX9CkGcE= +github.com/pion/turn/v3 v3.0.3/go.mod h1:vw0Dz420q7VYAF3J4wJKzReLHIo2LGp4ev8nXQexYsc= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sclevine/agouti v3.0.0+incompatible h1:8IBJS6PWz3uTlMP3YBIR5f+KAldcGuOeFkFbUWfBgK4= @@ -101,8 +96,9 @@ golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -120,8 +116,9 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -147,8 +144,9 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -165,8 +163,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -186,7 +184,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= diff --git a/icegatherer.go b/icegatherer.go index 51ea1aff79c..232145a974d 100644 --- a/icegatherer.go +++ b/icegatherer.go @@ -124,6 +124,7 @@ func (g *ICEGatherer) createAgent() error { ProxyDialer: g.api.settingEngine.iceProxyDialer, DisableActiveTCP: g.api.settingEngine.iceDisableActiveTCP, MaxBindingRequests: g.api.settingEngine.iceMaxBindingRequests, + BindingRequestHandler: g.api.settingEngine.iceBindingRequestHandler, } requestedNetworkTypes := g.api.settingEngine.candidates.ICENetworkTypes diff --git a/interceptor.go b/interceptor.go index fec45ab7e0f..3ec09f7ac48 100644 --- a/interceptor.go +++ b/interceptor.go @@ -127,7 +127,7 @@ func ConfigureCongestionControlFeedback(mediaEngine *MediaEngine, interceptorReg return nil } -// ConfigureSimulcastExtensionHeaders enables the RTP Extenison 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 { return err diff --git a/interceptor_test.go b/interceptor_test.go index 31cfe058341..2670627df74 100644 --- a/interceptor_test.go +++ b/interceptor_test.go @@ -33,9 +33,6 @@ func TestPeerConnection_Interceptor(t *testing.T) { defer report() createPC := func() *PeerConnection { - m := &MediaEngine{} - assert.NoError(t, m.RegisterDefaultCodecs()) - ir := &interceptor.Registry{} ir.Add(&mock_interceptor.Factory{ NewInterceptorFn: func(_ string) (interceptor.Interceptor, error) { @@ -64,7 +61,7 @@ func TestPeerConnection_Interceptor(t *testing.T) { }, }) - pc, err := NewAPI(WithMediaEngine(m), WithInterceptorRegistry(ir)).NewPeerConnection(Configuration{}) + pc, err := NewAPI(WithInterceptorRegistry(ir)).NewPeerConnection(Configuration{}) assert.NoError(t, err) return pc @@ -115,9 +112,6 @@ func Test_Interceptor_BindUnbind(t *testing.T) { report := test.CheckRoutines(t) defer report() - m := &MediaEngine{} - assert.NoError(t, m.RegisterDefaultCodecs()) - var ( cntBindRTCPReader uint32 cntBindRTCPWriter uint32 @@ -160,7 +154,7 @@ func Test_Interceptor_BindUnbind(t *testing.T) { NewInterceptorFn: func(_ string) (interceptor.Interceptor, error) { return mockInterceptor, nil }, }) - sender, receiver, err := NewAPI(WithMediaEngine(m), WithInterceptorRegistry(ir)).newPair(Configuration{}) + sender, receiver, err := NewAPI(WithInterceptorRegistry(ir)).newPair(Configuration{}) assert.NoError(t, err) track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") @@ -231,12 +225,59 @@ func Test_InterceptorRegistry_Build(t *testing.T) { }, }) - peerConnectionA, err := NewAPI(WithInterceptorRegistry(ir)).NewPeerConnection(Configuration{}) - assert.NoError(t, err) - - peerConnectionB, err := NewAPI(WithInterceptorRegistry(ir)).NewPeerConnection(Configuration{}) + peerConnectionA, peerConnectionB, err := NewAPI(WithInterceptorRegistry(ir)).newPair(Configuration{}) assert.NoError(t, err) assert.Equal(t, 2, registryBuildCount) closePairNow(t, peerConnectionA, peerConnectionB) } + +func Test_Interceptor_ZeroSSRC(t *testing.T) { + to := test.TimeOut(time.Second * 20) + defer to.Stop() + + report := test.CheckRoutines(t) + defer report() + + track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") + assert.NoError(t, err) + + offerer, answerer, err := newPair() + assert.NoError(t, err) + + _, err = offerer.AddTrack(track) + assert.NoError(t, err) + + probeReceiverCreated := make(chan struct{}) + + go func() { + sequenceNumber := uint16(0) + for range time.NewTicker(time.Millisecond * 20).C { + track.mu.Lock() + if len(track.bindings) == 1 { + _, err = track.bindings[0].writeStream.WriteRTP(&rtp.Header{ + Version: 2, + SSRC: 0, + SequenceNumber: sequenceNumber, + }, []byte{0, 1, 2, 3, 4, 5}) + assert.NoError(t, err) + } + sequenceNumber++ + track.mu.Unlock() + + if nonMediaBandwidthProbe, ok := answerer.nonMediaBandwidthProbe.Load().(*RTPReceiver); ok { + assert.Equal(t, len(nonMediaBandwidthProbe.Tracks()), 1) + close(probeReceiverCreated) + return + } + } + }() + + assert.NoError(t, signalPair(offerer, answerer)) + + peerConnectionConnected := untilConnectionState(PeerConnectionStateConnected, offerer, answerer) + peerConnectionConnected.Wait() + + <-probeReceiverCreated + closePairNow(t, offerer, answerer) +} diff --git a/internal/fmtp/av1.go b/internal/fmtp/av1.go new file mode 100644 index 00000000000..29eccd114d0 --- /dev/null +++ b/internal/fmtp/av1.go @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package fmtp + +type av1FMTP struct { + parameters map[string]string +} + +func (h *av1FMTP) MimeType() string { + return "video/av1" +} + +func (h *av1FMTP) Match(b FMTP) bool { + c, ok := b.(*av1FMTP) + if !ok { + return false + } + + // RTP Payload Format For AV1 (v1.0) + // https://aomediacodec.github.io/av1-rtp-spec/ + // If the profile parameter is not present, it MUST be inferred to be 0 (“Main” profile). + hProfile, ok := h.parameters["profile"] + if !ok { + hProfile = "0" + } + cProfile, ok := c.parameters["profile"] + if !ok { + cProfile = "0" + } + if hProfile != cProfile { + return false + } + + return true +} + +func (h *av1FMTP) Parameter(key string) (string, bool) { + v, ok := h.parameters[key] + return v, ok +} diff --git a/internal/fmtp/fmtp.go b/internal/fmtp/fmtp.go index 5461c019bd2..f515a648d01 100644 --- a/internal/fmtp/fmtp.go +++ b/internal/fmtp/fmtp.go @@ -8,6 +8,22 @@ import ( "strings" ) +func parseParameters(line string) map[string]string { + parameters := make(map[string]string) + + for _, p := range strings.Split(line, ";") { + pp := strings.SplitN(strings.TrimSpace(p), "=", 2) + key := strings.ToLower(pp[0]) + var value string + if len(pp) > 1 { + value = pp[1] + } + parameters[key] = value + } + + return parameters +} + // FMTP interface for implementing custom // FMTP parsers based on MimeType type FMTP interface { @@ -23,29 +39,30 @@ type FMTP interface { } // Parse parses an fmtp string based on the MimeType -func Parse(mimetype, line string) FMTP { +func Parse(mimeType, line string) FMTP { var f FMTP - parameters := make(map[string]string) - - for _, p := range strings.Split(line, ";") { - pp := strings.SplitN(strings.TrimSpace(p), "=", 2) - key := strings.ToLower(pp[0]) - var value string - if len(pp) > 1 { - value = pp[1] - } - parameters[key] = value - } + parameters := parseParameters(line) switch { - case strings.EqualFold(mimetype, "video/h264"): + case strings.EqualFold(mimeType, "video/h264"): f = &h264FMTP{ parameters: parameters, } + + case strings.EqualFold(mimeType, "video/vp9"): + f = &vp9FMTP{ + parameters: parameters, + } + + case strings.EqualFold(mimeType, "video/av1"): + f = &av1FMTP{ + parameters: parameters, + } + default: f = &genericFMTP{ - mimeType: mimetype, + mimeType: mimeType, parameters: parameters, } } diff --git a/internal/fmtp/fmtp_test.go b/internal/fmtp/fmtp_test.go index 2bc2bc873e7..96c1a8e7434 100644 --- a/internal/fmtp/fmtp_test.go +++ b/internal/fmtp/fmtp_test.go @@ -8,127 +8,513 @@ import ( "testing" ) -func TestGenericParseFmtp(t *testing.T) { - testCases := map[string]struct { - input string +func TestParseParameters(t *testing.T) { + for _, ca := range []struct { + name string + line string + parameters map[string]string + }{ + { + "one param", + "key-name=value", + map[string]string{ + "key-name": "value", + }, + }, + { + "one param with white spaces", + "\tkey-name=value ", + map[string]string{ + "key-name": "value", + }, + }, + { + "two params", + "key-name=value;key2=value2", + map[string]string{ + "key-name": "value", + "key2": "value2", + }, + }, + { + "two params with white spaces", + "key-name=value; \n\tkey2=value2 ", + map[string]string{ + "key-name": "value", + "key2": "value2", + }, + }, + } { + t.Run(ca.name, func(t *testing.T) { + parameters := parseParameters(ca.line) + if !reflect.DeepEqual(parameters, ca.parameters) { + t.Errorf("expected '%v', got '%v'", ca.parameters, parameters) + } + }) + } +} + +func TestParse(t *testing.T) { + for _, ca := range []struct { + name string + mimeType string + line string expected FMTP }{ - "OneParam": { - input: "key-name=value", - expected: &genericFMTP{ + { + "generic", + "generic", + "key-name=value", + &genericFMTP{ mimeType: "generic", parameters: map[string]string{ "key-name": "value", }, }, }, - "OneParamWithWhiteSpeces": { - input: "\tkey-name=value ", - expected: &genericFMTP{ + { + "generic case normalization", + "generic", + "Key=value", + &genericFMTP{ mimeType: "generic", + parameters: map[string]string{ + "key": "value", + }, + }, + }, + { + "h264", + "video/h264", + "key-name=value", + &h264FMTP{ parameters: map[string]string{ "key-name": "value", }, }, }, - "TwoParams": { - input: "key-name=value;key2=value2", - expected: &genericFMTP{ - mimeType: "generic", + { + "vp9", + "video/vp9", + "key-name=value", + &vp9FMTP{ parameters: map[string]string{ "key-name": "value", - "key2": "value2", }, }, }, - "TwoParamsWithWhiteSpeces": { - input: "key-name=value; \n\tkey2=value2 ", - expected: &genericFMTP{ - mimeType: "generic", + { + "av1", + "video/av1", + "key-name=value", + &av1FMTP{ parameters: map[string]string{ "key-name": "value", - "key2": "value2", }, }, }, - } - for name, testCase := range testCases { - testCase := testCase - t.Run(name, func(t *testing.T) { - f := Parse("generic", testCase.input) - if !reflect.DeepEqual(testCase.expected, f) { - t.Errorf("Expected Fmtp params: %v, got: %v", testCase.expected, f) + } { + t.Run(ca.name, func(t *testing.T) { + f := Parse(ca.mimeType, ca.line) + if !reflect.DeepEqual(ca.expected, f) { + t.Errorf("expected '%v', got '%v'", ca.expected, f) } - if f.MimeType() != "generic" { - t.Errorf("Expected MimeType of generic, got: %s", f.MimeType()) + if f.MimeType() != ca.mimeType { + t.Errorf("Expected '%v', got '%s'", ca.mimeType, f.MimeType()) } }) } } -func TestGenericFmtpCompare(t *testing.T) { +func TestMatch(t *testing.T) { consistString := map[bool]string{true: "consist", false: "inconsist"} - testCases := map[string]struct { - a, b string + for _, ca := range []struct { + name string + a FMTP + b FMTP consist bool }{ - "Equal": { - a: "key1=value1;key2=value2;key3=value3", - b: "key1=value1;key2=value2;key3=value3", - consist: true, - }, - "EqualWithWhitespaceVariants": { - a: "key1=value1;key2=value2;key3=value3", - b: " key1=value1; \nkey2=value2;\t\nkey3=value3", - consist: true, - }, - "EqualWithCase": { - a: "key1=value1;key2=value2;key3=value3", - b: "key1=value1;key2=Value2;Key3=value3", - consist: true, - }, - "OneHasExtraParam": { - a: "key1=value1;key2=value2;key3=value3", - b: "key1=value1;key2=value2;key3=value3;key4=value4", - consist: true, - }, - "Inconsistent": { - a: "key1=value1;key2=value2;key3=value3", - b: "key1=value1;key2=different_value;key3=value3", - consist: false, - }, - "Inconsistent_OneHasExtraParam": { - a: "key1=value1;key2=value2;key3=value3;key4=value4", - b: "key1=value1;key2=different_value;key3=value3", - consist: false, + { + "generic equal", + &genericFMTP{ + mimeType: "generic", + parameters: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + }, + &genericFMTP{ + mimeType: "generic", + parameters: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + }, + true, }, - } - for name, testCase := range testCases { - testCase := testCase - check := func(t *testing.T, a, b string) { - aa := Parse("", a) - bb := Parse("", b) - c := aa.Match(bb) - if c != testCase.consist { + { + "generic one extra param", + &genericFMTP{ + mimeType: "generic", + parameters: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + }, + &genericFMTP{ + mimeType: "generic", + parameters: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + "key4": "value4", + }, + }, + true, + }, + { + "generic inconsistent different kind", + &genericFMTP{ + mimeType: "generic", + parameters: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + }, + &h264FMTP{}, + false, + }, + { + "generic inconsistent different mime type", + &genericFMTP{ + mimeType: "generic1", + parameters: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + }, + &genericFMTP{ + mimeType: "generic2", + parameters: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + }, + false, + }, + { + "generic inconsistent different parameters", + &genericFMTP{ + mimeType: "generic", + parameters: map[string]string{ + "key1": "value1", + "key2": "value2", + "key3": "value3", + }, + }, + &genericFMTP{ + mimeType: "generic", + parameters: map[string]string{ + "key1": "value1", + "key2": "different_value", + "key3": "value3", + }, + }, + false, + }, + { + "h264 equal", + &h264FMTP{ + parameters: map[string]string{ + "level-asymmetry-allowed": "1", + "packetization-mode": "1", + "profile-level-id": "42e01f", + }, + }, + &h264FMTP{ + parameters: map[string]string{ + "level-asymmetry-allowed": "1", + "packetization-mode": "1", + "profile-level-id": "42e01f", + }, + }, + true, + }, + { + "h264 one extra param", + &h264FMTP{ + parameters: map[string]string{ + "level-asymmetry-allowed": "1", + "packetization-mode": "1", + "profile-level-id": "42e01f", + }, + }, + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "1", + "profile-level-id": "42e01f", + }, + }, + true, + }, + { + "h264 different profile level ids version", + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "1", + "profile-level-id": "42e01f", + }, + }, + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "1", + "profile-level-id": "42e029", + }, + }, + true, + }, + { + "h264 inconsistent different kind", + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "0", + "profile-level-id": "42e01f", + }, + }, + &genericFMTP{}, + false, + }, + { + "h264 inconsistent different parameters", + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "0", + "profile-level-id": "42e01f", + }, + }, + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "1", + "profile-level-id": "42e01f", + }, + }, + false, + }, + { + "h264 inconsistent missing packetization mode", + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "0", + "profile-level-id": "42e01f", + }, + }, + &h264FMTP{ + parameters: map[string]string{ + "profile-level-id": "42e01f", + }, + }, + false, + }, + { + "h264 inconsistent missing profile level id", + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "1", + "profile-level-id": "42e01f", + }, + }, + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "1", + }, + }, + false, + }, + { + "h264 inconsistent invalid profile level id", + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "1", + "profile-level-id": "42e029", + }, + }, + &h264FMTP{ + parameters: map[string]string{ + "packetization-mode": "1", + "profile-level-id": "41e029", + }, + }, + false, + }, + { + "vp9 equal", + &vp9FMTP{ + parameters: map[string]string{ + "profile-id": "1", + }, + }, + &vp9FMTP{ + parameters: map[string]string{ + "profile-id": "1", + }, + }, + true, + }, + { + "vp9 missing profile", + &vp9FMTP{ + parameters: map[string]string{}, + }, + &vp9FMTP{ + parameters: map[string]string{}, + }, + true, + }, + { + "vp9 inferred profile", + &vp9FMTP{ + parameters: map[string]string{ + "profile-id": "0", + }, + }, + &vp9FMTP{ + parameters: map[string]string{}, + }, + true, + }, + { + "vp9 inconsistent different kind", + &vp9FMTP{ + parameters: map[string]string{ + "profile-id": "0", + }, + }, + &genericFMTP{}, + false, + }, + { + "vp9 inconsistent different profile", + &vp9FMTP{ + parameters: map[string]string{ + "profile-id": "0", + }, + }, + &vp9FMTP{ + parameters: map[string]string{ + "profile-id": "1", + }, + }, + false, + }, + { + "vp9 inconsistent different inferred profile", + &vp9FMTP{ + parameters: map[string]string{}, + }, + &vp9FMTP{ + parameters: map[string]string{ + "profile-id": "1", + }, + }, + false, + }, + { + "av1 equal", + &av1FMTP{ + parameters: map[string]string{ + "profile": "1", + }, + }, + &av1FMTP{ + parameters: map[string]string{ + "profile": "1", + }, + }, + true, + }, + { + "av1 missing profile", + &av1FMTP{ + parameters: map[string]string{}, + }, + &av1FMTP{ + parameters: map[string]string{}, + }, + true, + }, + { + "av1 inferred profile", + &av1FMTP{ + parameters: map[string]string{ + "profile": "0", + }, + }, + &av1FMTP{ + parameters: map[string]string{}, + }, + true, + }, + { + "av1 inconsistent different kind", + &av1FMTP{ + parameters: map[string]string{ + "profile": "0", + }, + }, + &genericFMTP{}, + false, + }, + { + "av1 inconsistent different profile", + &av1FMTP{ + parameters: map[string]string{ + "profile": "0", + }, + }, + &av1FMTP{ + parameters: map[string]string{ + "profile": "1", + }, + }, + false, + }, + { + "av1 inconsistent different inferred profile", + &av1FMTP{ + parameters: map[string]string{}, + }, + &av1FMTP{ + parameters: map[string]string{ + "profile": "1", + }, + }, + false, + }, + } { + t.Run(ca.name, func(t *testing.T) { + c := ca.a.Match(ca.b) + if c != ca.consist { t.Errorf( "'%s' and '%s' are expected to be %s, but treated as %s", - a, b, consistString[testCase.consist], consistString[c], + ca.a, ca.b, consistString[ca.consist], consistString[c], ) } - // test reverse case here - c = bb.Match(aa) - if c != testCase.consist { + c = ca.b.Match(ca.a) + if c != ca.consist { t.Errorf( "'%s' and '%s' are expected to be %s, but treated as %s", - a, b, consistString[testCase.consist], consistString[c], + ca.a, ca.b, consistString[ca.consist], consistString[c], ) } - } - t.Run(name, func(t *testing.T) { - check(t, testCase.a, testCase.b) }) } } diff --git a/internal/fmtp/h264_test.go b/internal/fmtp/h264_test.go deleted file mode 100644 index 93da80d5e71..00000000000 --- a/internal/fmtp/h264_test.go +++ /dev/null @@ -1,145 +0,0 @@ -// SPDX-FileCopyrightText: 2023 The Pion community -// SPDX-License-Identifier: MIT - -package fmtp - -import ( - "reflect" - "testing" -) - -func TestH264FMTPParse(t *testing.T) { - testCases := map[string]struct { - input string - expected FMTP - }{ - "OneParam": { - input: "key-name=value", - expected: &h264FMTP{ - parameters: map[string]string{ - "key-name": "value", - }, - }, - }, - "OneParamWithWhiteSpeces": { - input: "\tkey-name=value ", - expected: &h264FMTP{ - parameters: map[string]string{ - "key-name": "value", - }, - }, - }, - "TwoParams": { - input: "key-name=value;key2=value2", - expected: &h264FMTP{ - parameters: map[string]string{ - "key-name": "value", - "key2": "value2", - }, - }, - }, - "TwoParamsWithWhiteSpeces": { - input: "key-name=value; \n\tkey2=value2 ", - expected: &h264FMTP{ - parameters: map[string]string{ - "key-name": "value", - "key2": "value2", - }, - }, - }, - } - for name, testCase := range testCases { - testCase := testCase - t.Run(name, func(t *testing.T) { - f := Parse("video/h264", testCase.input) - if !reflect.DeepEqual(testCase.expected, f) { - t.Errorf("Expected Fmtp params: %v, got: %v", testCase.expected, f) - } - - if f.MimeType() != "video/h264" { - t.Errorf("Expected MimeType of video/h264, got: %s", f.MimeType()) - } - }) - } -} - -func TestH264FMTPCompare(t *testing.T) { - consistString := map[bool]string{true: "consist", false: "inconsist"} - - testCases := map[string]struct { - a, b string - consist bool - }{ - "Equal": { - a: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", - b: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", - consist: true, - }, - "EqualWithWhitespaceVariants": { - a: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", - b: " level-asymmetry-allowed=1; \npacketization-mode=1;\t\nprofile-level-id=42e01f", - consist: true, - }, - "EqualWithCase": { - a: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", - b: "level-asymmetry-allowed=1;packetization-mode=1;PROFILE-LEVEL-ID=42e01f", - consist: true, - }, - "OneHasExtraParam": { - a: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", - b: "packetization-mode=1;profile-level-id=42e01f", - consist: true, - }, - "DifferentProfileLevelIDVersions": { - a: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f", - b: "packetization-mode=1;profile-level-id=42e029", - consist: true, - }, - "Inconsistent": { - a: "packetization-mode=1;profile-level-id=42e029", - b: "packetization-mode=0;profile-level-id=42e029", - consist: false, - }, - "Inconsistent_MissingPacketizationMode": { - a: "packetization-mode=1;profile-level-id=42e029", - b: "profile-level-id=42e029", - consist: false, - }, - "Inconsistent_MissingProfileLevelID": { - a: "packetization-mode=1;profile-level-id=42e029", - b: "packetization-mode=1", - consist: false, - }, - "Inconsistent_InvalidProfileLevelID": { - a: "packetization-mode=1;profile-level-id=42e029", - b: "level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=41e029", - consist: false, - }, - } - for name, testCase := range testCases { - testCase := testCase - check := func(t *testing.T, a, b string) { - aa := Parse("video/h264", a) - bb := Parse("video/h264", b) - c := aa.Match(bb) - if c != testCase.consist { - t.Errorf( - "'%s' and '%s' are expected to be %s, but treated as %s", - a, b, consistString[testCase.consist], consistString[c], - ) - } - - // test reverse case here - c = bb.Match(aa) - if c != testCase.consist { - t.Errorf( - "'%s' and '%s' are expected to be %s, but treated as %s", - a, b, consistString[testCase.consist], consistString[c], - ) - } - } - t.Run(name, func(t *testing.T) { - check(t, testCase.a, testCase.b) - }) - } -} diff --git a/internal/fmtp/vp9.go b/internal/fmtp/vp9.go new file mode 100644 index 00000000000..7fc618bccd8 --- /dev/null +++ b/internal/fmtp/vp9.go @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2023 The Pion community +// SPDX-License-Identifier: MIT + +package fmtp + +type vp9FMTP struct { + parameters map[string]string +} + +func (h *vp9FMTP) MimeType() string { + return "video/vp9" +} + +func (h *vp9FMTP) Match(b FMTP) bool { + c, ok := b.(*vp9FMTP) + if !ok { + return false + } + + // RTP Payload Format for VP9 Video - draft-ietf-payload-vp9-16 + // https://datatracker.ietf.org/doc/html/draft-ietf-payload-vp9-16 + // If no profile-id is present, Profile 0 MUST be inferred + hProfileID, ok := h.parameters["profile-id"] + if !ok { + hProfileID = "0" + } + cProfileID, ok := c.parameters["profile-id"] + if !ok { + cProfileID = "0" + } + if hProfileID != cProfileID { + return false + } + + return true +} + +func (h *vp9FMTP) Parameter(key string) (string, bool) { + v, ok := h.parameters[key] + return v, ok +} diff --git a/internal/util/util.go b/internal/util/util.go index 3f43c123e53..966a6230a68 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -18,12 +18,12 @@ const ( // Use global random generator to properly seed by crypto grade random. var globalMathRandomGenerator = randutil.NewMathRandomGenerator() // nolint:gochecknoglobals -// MathRandAlpha generates a mathmatical random alphabet sequence of the requested length. +// MathRandAlpha generates a mathematical random alphabet sequence of the requested length. func MathRandAlpha(n int) string { return globalMathRandomGenerator.GenerateString(n, runesAlpha) } -// RandUint32 generates a mathmatical random uint32. +// RandUint32 generates a mathematical random uint32. func RandUint32() uint32 { return globalMathRandomGenerator.Uint32() } diff --git a/mediaengine.go b/mediaengine.go index cbdcfb6d7d5..47a08d2f24c 100644 --- a/mediaengine.go +++ b/mediaengine.go @@ -455,6 +455,30 @@ func (m *MediaEngine) matchRemoteCodec(remoteCodec RTPCodecParameters, typ RTPCo return matchType, nil } +// Update header extensions from a remote media section +func (m *MediaEngine) updateHeaderExtensionFromMediaSection(media *sdp.MediaDescription) error { + var typ RTPCodecType + switch { + case strings.EqualFold(media.MediaName.Media, "audio"): + typ = RTPCodecTypeAudio + case strings.EqualFold(media.MediaName.Media, "video"): + typ = RTPCodecTypeVideo + default: + return nil + } + extensions, err := rtpExtensionsFromMediaDescription(media) + if err != nil { + return err + } + + for extension, id := range extensions { + if err = m.updateHeaderExtension(id, extension, typ); err != nil { + return err + } + } + return nil +} + // 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 { @@ -498,14 +522,27 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e for _, media := range desc.MediaDescriptions { var typ RTPCodecType + switch { - case !m.negotiatedAudio && strings.EqualFold(media.MediaName.Media, "audio"): - m.negotiatedAudio = true + case strings.EqualFold(media.MediaName.Media, "audio"): typ = RTPCodecTypeAudio - case !m.negotiatedVideo && strings.EqualFold(media.MediaName.Media, "video"): - m.negotiatedVideo = true + case strings.EqualFold(media.MediaName.Media, "video"): typ = RTPCodecTypeVideo + } + + switch { + case !m.negotiatedAudio && typ == RTPCodecTypeAudio: + m.negotiatedAudio = true + case !m.negotiatedVideo && typ == RTPCodecTypeVideo: + m.negotiatedVideo = true default: + // update header extesions from remote sdp if codec is negotiated, Firefox + // would send updated header extension in renegotiation. + // e.g. publish first track without simucalst ->negotiated-> publish second track with simucalst + // then the two media secontions have different rtp header extensions in offer + if err := m.updateHeaderExtensionFromMediaSection(media); err != nil { + return err + } continue } @@ -541,16 +578,9 @@ func (m *MediaEngine) updateFromRemoteDescription(desc sdp.SessionDescription) e continue } - extensions, err := rtpExtensionsFromMediaDescription(media) - if err != nil { + if err := m.updateHeaderExtensionFromMediaSection(media); err != nil { return err } - - for extension, id := range extensions { - if err = m.updateHeaderExtension(id, extension, typ); err != nil { - return err - } - } } return nil } diff --git a/mediaengine_test.go b/mediaengine_test.go index d9a9e7b1ff4..786cbcac0af 100644 --- a/mediaengine_test.go +++ b/mediaengine_test.go @@ -212,6 +212,39 @@ a=rtpmap:111 opus/48000/2 assert.False(t, midVideoEnabled) }) + t.Run("Different Header Extensions on same codec", func(t *testing.T) { + const headerExtensions = `v=0 +o=- 4596489990601351948 2 IN IP4 127.0.0.1 +s=- +t=0 0 +m=audio 9 UDP/TLS/RTP/SAVPF 111 +a=rtpmap:111 opus/48000/2 +m=audio 9 UDP/TLS/RTP/SAVPF 111 +a=extmap:7 urn:ietf:params:rtp-hdrext:sdes:mid +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))) + + assert.False(t, m.negotiatedVideo) + assert.True(t, m.negotiatedAudio) + + absID, absAudioEnabled, absVideoEnabled := m.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}) + assert.Equal(t, midID, 7) + assert.True(t, midAudioEnabled) + assert.False(t, midVideoEnabled) + }) + t.Run("Prefers exact codec matches", func(t *testing.T) { const profileLevels = `v=0 o=- 4596489990601351948 2 IN IP4 127.0.0.1 @@ -308,7 +341,7 @@ a=rtpmap:96 VP8/90000 o=- 4596489990601351948 2 IN IP4 127.0.0.1 s=- t=0 0 -m=video 60323 UDP/TLS/RTP/SAVPF 94 95 106 107 108 109 96 97 +m=video 60323 UDP/TLS/RTP/SAVPF 94 95 106 107 108 109 96 97 a=rtpmap:94 VP8/90000 a=rtpmap:95 rtx/90000 a=fmtp:95 apt=94 diff --git a/operations.go b/operations.go index d9dca4a8e48..bc366ac34db 100644 --- a/operations.go +++ b/operations.go @@ -16,11 +16,19 @@ type operations struct { mu sync.Mutex busy bool ops *list.List + + updateNegotiationNeededFlagOnEmptyChain *atomicBool + onNegotiationNeeded func() } -func newOperations() *operations { +func newOperations( + updateNegotiationNeededFlagOnEmptyChain *atomicBool, + onNegotiationNeeded func(), +) *operations { return &operations{ - ops: list.New(), + ops: list.New(), + updateNegotiationNeededFlagOnEmptyChain: updateNegotiationNeededFlagOnEmptyChain, + onNegotiationNeeded: onNegotiationNeeded, } } @@ -93,4 +101,9 @@ func (o *operations) start() { fn() fn = o.pop() } + if !o.updateNegotiationNeededFlagOnEmptyChain.get() { + return + } + o.updateNegotiationNeededFlagOnEmptyChain.set(false) + o.onNegotiationNeeded() } diff --git a/operations_test.go b/operations_test.go index f426549f6c5..428c2b4df97 100644 --- a/operations_test.go +++ b/operations_test.go @@ -4,19 +4,31 @@ package webrtc import ( + "sync" "testing" "github.com/stretchr/testify/assert" ) func TestOperations_Enqueue(t *testing.T) { - ops := newOperations() - for i := 0; i < 100; i++ { + updateNegotiationNeededFlagOnEmptyChain := &atomicBool{} + onNegotiationNeededCalledCount := 0 + var onNegotiationNeededCalledCountMu sync.Mutex + ops := newOperations(updateNegotiationNeededFlagOnEmptyChain, func() { + onNegotiationNeededCalledCountMu.Lock() + onNegotiationNeededCalledCount++ + onNegotiationNeededCalledCountMu.Unlock() + }) + for resultSet := 0; resultSet < 100; resultSet++ { results := make([]int, 16) + resultSetCopy := resultSet for i := range results { func(j int) { ops.Enqueue(func() { results[j] = j * j + if resultSetCopy > 50 { + updateNegotiationNeededFlagOnEmptyChain.set(true) + } }) }(i) } @@ -26,9 +38,13 @@ func TestOperations_Enqueue(t *testing.T) { assert.Equal(t, len(expected), len(results)) assert.Equal(t, expected, results) } + onNegotiationNeededCalledCountMu.Lock() + defer onNegotiationNeededCalledCountMu.Unlock() + assert.NotEqual(t, onNegotiationNeededCalledCount, 0) } func TestOperations_Done(*testing.T) { - ops := newOperations() + ops := newOperations(&atomicBool{}, func() { + }) ops.Done() } diff --git a/ortc_media_test.go b/ortc_media_test.go index 5d9624166dd..7d81662e60e 100644 --- a/ortc_media_test.go +++ b/ortc_media_test.go @@ -26,9 +26,6 @@ func Test_ORTC_Media(t *testing.T) { stackA, stackB, err := newORTCPair() assert.NoError(t, err) - assert.NoError(t, stackA.api.mediaEngine.RegisterDefaultCodecs()) - assert.NoError(t, stackB.api.mediaEngine.RegisterDefaultCodecs()) - assert.NoError(t, signalORTCPair(stackA, stackB)) track, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") diff --git a/package.json b/package.json index 429f3efb342..f58d23972b6 100644 --- a/package.json +++ b/package.json @@ -3,9 +3,10 @@ "repository": "git@github.com:pion/webrtc.git", "private": true, "devDependencies": { - "wrtc": "0.4.7" + "@roamhq/wrtc": "^0.8.0" }, "dependencies": { "request": "2.88.2" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/peerconnection.go b/peerconnection.go index 5e2fba24cf1..5876e5f76cd 100644 --- a/peerconnection.go +++ b/peerconnection.go @@ -55,9 +55,9 @@ type PeerConnection struct { idpLoginURL *string - isClosed *atomicBool - isNegotiationNeeded *atomicBool - negotiationNeededState negotiationNeededState + isClosed *atomicBool + isNegotiationNeeded *atomicBool + updateNegotiationNeededFlagOnEmptyChain *atomicBool lastOffer string lastAnswer string @@ -68,7 +68,8 @@ type PeerConnection struct { // should be defined (see JSEP 3.4.1). greaterMid int - rtpTransceivers []*RTPTransceiver + rtpTransceivers []*RTPTransceiver + nonMediaBandwidthProbe atomic.Value // RTPReceiver onSignalingStateChangeHandler func(SignalingState) onICEConnectionStateChangeHandler atomic.Value // func(ICEConnectionState) @@ -104,6 +105,7 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection, // https://w3c.github.io/webrtc-pc/#constructor (Step #2) // Some variables defined explicitly despite their implicit zero values to // allow better readability to understand what is happening. + pc := &PeerConnection{ statsID: fmt.Sprintf("PeerConnection-%d", time.Now().UnixNano()), configuration: Configuration{ @@ -114,18 +116,19 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection, Certificates: []Certificate{}, ICECandidatePoolSize: 0, }, - ops: newOperations(), - isClosed: &atomicBool{}, - isNegotiationNeeded: &atomicBool{}, - negotiationNeededState: negotiationNeededStateEmpty, - lastOffer: "", - lastAnswer: "", - greaterMid: -1, - signalingState: SignalingStateStable, + isClosed: &atomicBool{}, + isNegotiationNeeded: &atomicBool{}, + updateNegotiationNeededFlagOnEmptyChain: &atomicBool{}, + lastOffer: "", + lastAnswer: "", + greaterMid: -1, + signalingState: SignalingStateStable, api: api, log: api.settingEngine.LoggerFactory.NewLogger("pc"), } + pc.ops = newOperations(pc.updateNegotiationNeededFlagOnEmptyChain, pc.onNegotiationNeeded) + pc.iceConnectionState.Store(ICEConnectionStateNew) pc.connectionState.Store(PeerConnectionStateNew) @@ -277,66 +280,54 @@ func (pc *PeerConnection) OnNegotiationNeeded(f func()) { // onNegotiationNeeded enqueues negotiationNeededOp if necessary // caller of this method should hold `pc.mu` lock +// https://www.w3.org/TR/webrtc/#dfn-update-the-negotiation-needed-flag func (pc *PeerConnection) onNegotiationNeeded() { - // https://w3c.github.io/webrtc-pc/#updating-the-negotiation-needed-flag - // non-canon step 1 - if pc.negotiationNeededState == negotiationNeededStateRun { - pc.negotiationNeededState = negotiationNeededStateQueue - return - } else if pc.negotiationNeededState == negotiationNeededStateQueue { + // 4.7.3.1 If the length of connection.[[Operations]] is not 0, then set + // connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to true, and abort these steps. + if !pc.ops.IsEmpty() { + pc.updateNegotiationNeededFlagOnEmptyChain.set(true) return } - pc.negotiationNeededState = negotiationNeededStateRun pc.ops.Enqueue(pc.negotiationNeededOp) } +// https://www.w3.org/TR/webrtc/#dfn-update-the-negotiation-needed-flag func (pc *PeerConnection) negotiationNeededOp() { - // Don't run NegotiatedNeeded checks if OnNegotiationNeeded is not set - if handler, ok := pc.onNegotiationNeededHandler.Load().(func()); !ok || handler == nil { - return - } - - // https://www.w3.org/TR/webrtc/#updating-the-negotiation-needed-flag - // Step 2.1 + // 4.7.3.2.1 If connection.[[IsClosed]] is true, abort these steps. if pc.isClosed.get() { return } - // non-canon step 2.2 + + // 4.7.3.2.2 If the length of connection.[[Operations]] is not 0, + // then set connection.[[UpdateNegotiationNeededFlagOnEmptyChain]] to + // true, and abort these steps. if !pc.ops.IsEmpty() { - pc.ops.Enqueue(pc.negotiationNeededOp) + pc.updateNegotiationNeededFlagOnEmptyChain.set(true) return } - // non-canon, run again if there was a request - defer func() { - pc.mu.Lock() - defer pc.mu.Unlock() - if pc.negotiationNeededState == negotiationNeededStateQueue { - defer pc.onNegotiationNeeded() - } - pc.negotiationNeededState = negotiationNeededStateEmpty - }() - - // Step 2.3 + // 4.7.3.2.3 If connection's signaling state is not "stable", abort these steps. if pc.SignalingState() != SignalingStateStable { return } - // Step 2.4 + // 4.7.3.2.4 If the result of checking if negotiation is needed is false, + // clear the negotiation-needed flag by setting connection.[[NegotiationNeeded]] + // to false, and abort these steps. if !pc.checkNegotiationNeeded() { pc.isNegotiationNeeded.set(false) return } - // Step 2.5 + // 4.7.3.2.5 If connection.[[NegotiationNeeded]] is already true, abort these steps. if pc.isNegotiationNeeded.get() { return } - // Step 2.6 + // 4.7.3.2.6 Set connection.[[NegotiationNeeded]] to true. pc.isNegotiationNeeded.set(true) - // Step 2.7 + // 4.7.3.2.7 Fire an event named negotiationneeded at connection. if handler, ok := pc.onNegotiationNeededHandler.Load().(func()); ok && handler != nil { handler() } @@ -1326,7 +1317,7 @@ func runIfNewReceiver( return false } -// configurepRTPReceivers opens knows inbound SRTP streams from the RemoteDescription +// configureRTPReceivers opens knows inbound SRTP streams from the RemoteDescription func (pc *PeerConnection) configureRTPReceivers(isRenegotiation bool, remoteDesc *SessionDescription, currentTransceivers []*RTPTransceiver) { //nolint:gocognit incomingTracks := trackDetailsFromSDP(pc.log, remoteDesc.parsed) @@ -1534,6 +1525,32 @@ func (pc *PeerConnection) handleUndeclaredSSRC(ssrc SSRC, remoteDescription *Ses return true, nil } +// Chrome sends probing traffic on SSRC 0. This reads the packets to ensure that we properly +// generate TWCC reports for it. Since this isn't actually media we don't pass this to the user +func (pc *PeerConnection) handleNonMediaBandwidthProbe() { + nonMediaBandwidthProbe, err := pc.api.NewRTPReceiver(RTPCodecTypeVideo, pc.dtlsTransport) + if err != nil { + pc.log.Errorf("handleNonMediaBandwidthProbe failed to create RTPReceiver: %v", err) + return + } + + if err = nonMediaBandwidthProbe.Receive(RTPReceiveParameters{ + Encodings: []RTPDecodingParameters{{RTPCodingParameters: RTPCodingParameters{}}}, + }); err != nil { + pc.log.Errorf("handleNonMediaBandwidthProbe failed to start RTPReceiver: %v", err) + return + } + + pc.nonMediaBandwidthProbe.Store(nonMediaBandwidthProbe) + b := make([]byte, pc.api.settingEngine.getReceiveMTU()) + for { + if _, _, err = nonMediaBandwidthProbe.readRTP(b, nonMediaBandwidthProbe.Track()); err != nil { + pc.log.Tracef("handleNonMediaBandwidthProbe read exiting: %v", err) + return + } + } +} + func (pc *PeerConnection) handleIncomingSSRC(rtpStream io.Reader, ssrc SSRC) error { //nolint:gocognit remoteDescription := pc.RemoteDescription() if remoteDescription == nil { @@ -1666,6 +1683,11 @@ func (pc *PeerConnection) undeclaredRTPMediaProcessor() { continue } + if ssrc == 0 { + go pc.handleNonMediaBandwidthProbe() + continue + } + pc.dtlsTransport.storeSimulcastStream(stream) if atomic.AddUint64(&simulcastRoutineCount, 1) >= simulcastMaxProbeRoutines { @@ -2082,6 +2104,9 @@ func (pc *PeerConnection) Close() error { closeErrs = append(closeErrs, t.Stop()) } } + if nonMediaBandwidthProbe, ok := pc.nonMediaBandwidthProbe.Load().(*RTPReceiver); ok { + closeErrs = append(closeErrs, nonMediaBandwidthProbe.Stop()) + } pc.mu.Unlock() // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-close (step #5) @@ -2124,10 +2149,11 @@ func (pc *PeerConnection) addRTPTransceiver(t *RTPTransceiver) { // by the ICEAgent since the offer or answer was created. func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription { pc.mu.Lock() + defer pc.mu.Unlock() + localDescription := pc.currentLocalDescription iceGather := pc.iceGatherer iceGatheringState := pc.ICEGatheringState() - pc.mu.Unlock() return populateLocalCandidates(localDescription, iceGather, iceGatheringState) } @@ -2137,10 +2163,11 @@ func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription { // PeerConnection is in the stable state, the value is null. func (pc *PeerConnection) PendingLocalDescription() *SessionDescription { pc.mu.Lock() + defer pc.mu.Unlock() + localDescription := pc.pendingLocalDescription iceGather := pc.iceGatherer iceGatheringState := pc.ICEGatheringState() - pc.mu.Unlock() return populateLocalCandidates(localDescription, iceGather, iceGatheringState) } @@ -2475,7 +2502,9 @@ func (pc *PeerConnection) generateMatchedSDP(transceivers []*RTPTransceiver, use sender.setNegotiated() } mediaTransceivers := []*RTPTransceiver{t} - mediaSections = append(mediaSections, mediaSection{id: midValue, transceivers: mediaTransceivers, ridMap: getRids(media)}) + + extensions, _ := rtpExtensionsFromMediaDescription(media) + mediaSections = append(mediaSections, mediaSection{id: midValue, transceivers: mediaTransceivers, matchExtensions: extensions, ridMap: getRids(media)}) } } diff --git a/peerconnection_go_test.go b/peerconnection_go_test.go index 1556e5a074b..8a554a2633e 100644 --- a/peerconnection_go_test.go +++ b/peerconnection_go_test.go @@ -1030,7 +1030,6 @@ func (r *trackRecords) remains() int { // This test assure that all track events emits. func TestPeerConnection_MassiveTracks(t *testing.T) { var ( - api = NewAPI() tRecs = &trackRecords{ trackIDs: make(map[string]struct{}), receivedTrackIDs: make(map[string]struct{}), @@ -1059,8 +1058,7 @@ func TestPeerConnection_MassiveTracks(t *testing.T) { connected = make(chan struct{}) stopped = make(chan struct{}) ) - assert.NoError(t, api.mediaEngine.RegisterDefaultCodecs()) - offerPC, answerPC, err := api.newPair(Configuration{}) + offerPC, answerPC, err := newPair() assert.NoError(t, err) // Create massive tracks. for range make([]struct{}, trackCount) { @@ -1608,3 +1606,37 @@ func TestPeerConnectionState(t *testing.T) { assert.NoError(t, pc.Close()) assert.Equal(t, PeerConnectionStateClosed, pc.ConnectionState()) } + +func TestPeerConnectionDeadlock(t *testing.T) { + lim := test.TimeOut(time.Second * 5) + defer lim.Stop() + + report := test.CheckRoutines(t) + defer report() + + closeHdlr := func(peerConnection *PeerConnection) { + peerConnection.OnICEConnectionStateChange(func(i ICEConnectionState) { + if i == ICEConnectionStateFailed || i == ICEConnectionStateClosed { + if err := peerConnection.Close(); err != nil { + assert.NoError(t, err) + } + } + }) + } + + pcOffer, pcAnswer, err := NewAPI().newPair(Configuration{}) + assert.NoError(t, err) + + assert.NoError(t, signalPair(pcOffer, pcAnswer)) + + onDataChannel, onDataChannelCancel := context.WithCancel(context.Background()) + pcAnswer.OnDataChannel(func(*DataChannel) { + onDataChannelCancel() + }) + <-onDataChannel.Done() + + closeHdlr(pcOffer) + closeHdlr(pcAnswer) + + closePairNow(t, pcOffer, pcAnswer) +} diff --git a/peerconnection_media_test.go b/peerconnection_media_test.go index 228cfed4bc2..121b41c68a1 100644 --- a/peerconnection_media_test.go +++ b/peerconnection_media_test.go @@ -13,6 +13,7 @@ import ( "errors" "fmt" "io" + "regexp" "strings" "sync" "sync/atomic" @@ -26,6 +27,7 @@ import ( "github.com/pion/sdp/v3" "github.com/pion/transport/v3/test" "github.com/pion/transport/v3/vnet" + "github.com/pion/webrtc/v4/internal/util" "github.com/pion/webrtc/v4/pkg/media" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -1078,6 +1080,9 @@ func TestPeerConnection_Simulcast_Probe(t *testing.T) { return })) + peerConnectionConnected := untilConnectionState(PeerConnectionStateConnected, pcOffer, pcAnswer) + peerConnectionConnected.Wait() + sequenceNumber := uint16(0) sendRTPPacket := func() { sequenceNumber++ @@ -1095,13 +1100,13 @@ func TestPeerConnection_Simulcast_Probe(t *testing.T) { sendRTPPacket() } - assert.NoError(t, signalPair(pcOffer, pcAnswer)) - trackRemoteChan := make(chan *TrackRemote, 1) pcAnswer.OnTrack(func(trackRemote *TrackRemote, _ *RTPReceiver) { trackRemoteChan <- trackRemote }) + assert.NoError(t, signalPair(pcOffer, pcAnswer)) + trackRemote := func() *TrackRemote { for { select { @@ -1388,7 +1393,223 @@ func TestPeerConnection_Simulcast(t *testing.T) { }) } -// Everytime we receieve a new SSRC we probe it and try to determine the proper way to handle it. +type simulcastTestTrackLocal struct { + *TrackLocalStaticRTP +} + +// don't use ssrc&payload in bindings to let the test write different stream packets. +func (s *simulcastTestTrackLocal) WriteRTP(pkt *rtp.Packet) error { + packet := getPacketAllocationFromPool() + + defer resetPacketPoolAllocation(packet) + + *packet = *pkt + + s.mu.RLock() + defer s.mu.RUnlock() + + writeErrs := []error{} + + for _, b := range s.bindings { + if _, err := b.writeStream.WriteRTP(&packet.Header, packet.Payload); err != nil { + writeErrs = append(writeErrs, err) + } + } + + return util.FlattenErrs(writeErrs) +} + +func TestPeerConnection_Simulcast_RTX(t *testing.T) { + lim := test.TimeOut(time.Second * 30) + defer lim.Stop() + + report := test.CheckRoutines(t) + defer report() + + rids := []string{"a", "b"} + pcOffer, pcAnswer, err := newPair() + assert.NoError(t, err) + + 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])) + assert.NoError(t, err) + + vp8WriterA, vp8WriterB := &simulcastTestTrackLocal{vp8WriterAStatic}, &simulcastTestTrackLocal{vp8WriterBStatic} + + sender, err := pcOffer.AddTrack(vp8WriterA) + assert.NoError(t, err) + assert.NotNil(t, sender) + + assert.NoError(t, sender.AddEncoding(vp8WriterB)) + + var ridMapLock sync.RWMutex + ridMap := map[string]int{} + + assertRidCorrect := func(t *testing.T) { + ridMapLock.Lock() + defer ridMapLock.Unlock() + + for _, rid := range rids { + assert.Equal(t, ridMap[rid], 1) + } + assert.Equal(t, len(ridMap), 2) + } + + ridsFullfilled := func() bool { + ridMapLock.Lock() + defer ridMapLock.Unlock() + + ridCount := len(ridMap) + return ridCount == 2 + } + + var rtxPacketRead atomic.Int32 + var wg sync.WaitGroup + wg.Add(2) + + pcAnswer.OnTrack(func(trackRemote *TrackRemote, _ *RTPReceiver) { + ridMapLock.Lock() + ridMap[trackRemote.RID()] = ridMap[trackRemote.RID()] + 1 + ridMapLock.Unlock() + + defer wg.Done() + + for { + _, attr, rerr := trackRemote.ReadRTP() + if rerr != nil { + break + } + if pt, ok := attr.Get(AttributeRtxPayloadType).(byte); ok { + if pt == 97 { + rtxPacketRead.Add(1) + } + } + } + }) + + parameters := sender.GetParameters() + assert.Equal(t, "a", parameters.Encodings[0].RID) + assert.Equal(t, "b", parameters.Encodings[1].RID) + + var midID, ridID, rsid uint8 + for _, extension := range parameters.HeaderExtensions { + switch extension.URI { + case sdp.SDESMidURI: + midID = uint8(extension.ID) + case sdp.SDESRTPStreamIDURI: + ridID = uint8(extension.ID) + case sdesRepairRTPStreamIDURI: + rsid = uint8(extension.ID) + } + } + assert.NotZero(t, midID) + assert.NotZero(t, ridID) + assert.NotZero(t, rsid) + + err = signalPairWithModification(pcOffer, pcAnswer, func(sdp string) string { + // 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) + + // padding only packets should not affect simulcast probe + var sequenceNumber uint16 + for sequenceNumber = 0; sequenceNumber < simulcastProbeCount+10; sequenceNumber++ { + time.Sleep(20 * time.Millisecond) + + for i, track := range []*simulcastTestTrackLocal{vp8WriterA, vp8WriterB} { + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: sequenceNumber, + PayloadType: 96, + Padding: true, + SSRC: uint32(i + 1), + }, + Payload: []byte{0x00, 0x02}, + } + + assert.NoError(t, track.WriteRTP(pkt)) + } + } + assert.False(t, ridsFullfilled(), "Simulcast probe should not be fulfilled by padding only packets") + + for ; !ridsFullfilled(); sequenceNumber++ { + time.Sleep(20 * time.Millisecond) + + for i, track := range []*simulcastTestTrackLocal{vp8WriterA, vp8WriterB} { + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: sequenceNumber, + PayloadType: 96, + SSRC: uint32(i + 1), + }, + Payload: []byte{0x00}, + } + assert.NoError(t, pkt.Header.SetExtension(midID, []byte("0"))) + assert.NoError(t, pkt.Header.SetExtension(ridID, []byte(track.RID()))) + + assert.NoError(t, track.WriteRTP(pkt)) + } + } + + assertRidCorrect(t) + + for i := 0; i < simulcastProbeCount+10; i++ { + sequenceNumber++ + time.Sleep(10 * time.Millisecond) + + for j, track := range []*simulcastTestTrackLocal{vp8WriterA, vp8WriterB} { + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: sequenceNumber, + PayloadType: 97, + SSRC: uint32(100 + j), + }, + Payload: []byte{0x00, 0x00, 0x00, 0x00, 0x00}, + } + assert.NoError(t, pkt.Header.SetExtension(midID, []byte("0"))) + assert.NoError(t, pkt.Header.SetExtension(ridID, []byte(track.RID()))) + assert.NoError(t, pkt.Header.SetExtension(rsid, []byte(track.RID()))) + + assert.NoError(t, track.WriteRTP(pkt)) + } + } + + for ; rtxPacketRead.Load() == 0; sequenceNumber++ { + time.Sleep(20 * time.Millisecond) + + for i, track := range []*simulcastTestTrackLocal{vp8WriterA, vp8WriterB} { + pkt := &rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SequenceNumber: sequenceNumber, + PayloadType: 96, + SSRC: uint32(i + 1), + }, + Payload: []byte{0x00}, + } + assert.NoError(t, pkt.Header.SetExtension(midID, []byte("0"))) + assert.NoError(t, pkt.Header.SetExtension(ridID, []byte(track.RID()))) + + assert.NoError(t, track.WriteRTP(pkt)) + } + } + + closePairNow(t, pcOffer, pcAnswer) + + wg.Wait() + + assert.Greater(t, rtxPacketRead.Load(), int32(0), "no rtx packet read") +} + +// Everytime we receive a new SSRC we probe it and try to determine the proper way to handle it. // In most cases a Track explicitly declares a SSRC and a OnTrack is fired. In two cases we don't // know the SSRC ahead of time // * Undeclared SSRC in a single media section (https://github.com/pion/webrtc/issues/880) @@ -1534,26 +1755,28 @@ func TestPeerConnection_Zero_PayloadType(t *testing.T) { assert.NoError(t, signalPair(pcOffer, pcAnswer)) - onTrackFired, onTrackFiredCancel := context.WithCancel(context.Background()) + trackFired := make(chan struct{}) + pcAnswer.OnTrack(func(track *TrackRemote, _ *RTPReceiver) { require.Equal(t, track.Codec().MimeType, MimeTypePCMU) - onTrackFiredCancel() + close(trackFired) }) - go func() { + func() { ticker := time.NewTicker(20 * time.Millisecond) defer ticker.Stop() - select { - case <-onTrackFired.Done(): - return - case <-ticker.C: - if routineErr := audioTrack.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); routineErr != nil { - fmt.Println(routineErr) + for { + select { + case <-trackFired: + return + case <-ticker.C: + if routineErr := audioTrack.WriteSample(media.Sample{Data: []byte{0x00}, Duration: time.Second}); routineErr != nil { + fmt.Println(routineErr) + } } } }() - <-onTrackFired.Done() closePairNow(t, pcOffer, pcAnswer) } diff --git a/peerconnection_renegotiation_test.go b/peerconnection_renegotiation_test.go index ac923d9dab9..6f4465475f2 100644 --- a/peerconnection_renegotiation_test.go +++ b/peerconnection_renegotiation_test.go @@ -603,7 +603,6 @@ func TestPeerConnection_Renegotiation_Trickle(t *testing.T) { settingEngine := SettingEngine{} api := NewAPI(WithSettingEngine(settingEngine)) - assert.NoError(t, api.mediaEngine.RegisterDefaultCodecs()) // Invalid STUN server on purpose, will stop ICE Gathering from completing in time pcOffer, pcAnswer, err := api.newPair(Configuration{ @@ -1047,7 +1046,7 @@ func TestPeerConnection_Renegotiation_Simulcast(t *testing.T) { for ssrc, rid := range rids { header := &rtp.Header{ Version: 2, - SSRC: uint32(ssrc), + SSRC: uint32(ssrc + 1), SequenceNumber: sequenceNumber, PayloadType: 96, } diff --git a/peerconnectionstate.go b/peerconnectionstate.go index 9ff712112a7..0ff24afca04 100644 --- a/peerconnectionstate.go +++ b/peerconnectionstate.go @@ -87,14 +87,3 @@ func (t PeerConnectionState) String() string { return ErrUnknownType.Error() } } - -type negotiationNeededState int - -const ( - // NegotiationNeededStateEmpty not running and queue is empty - negotiationNeededStateEmpty = iota - // NegotiationNeededStateEmpty running and queue is empty - negotiationNeededStateRun - // NegotiationNeededStateEmpty running and queue - negotiationNeededStateQueue -) diff --git a/pkg/media/oggreader/oggreader.go b/pkg/media/oggreader/oggreader.go index 50d1a7cd87b..5aaeb8b94eb 100644 --- a/pkg/media/oggreader/oggreader.go +++ b/pkg/media/oggreader/oggreader.go @@ -189,6 +189,8 @@ func (o *OggReader) ParseNextPage() ([]byte, *OggPageHeader, error) { } } + o.bytesReadSuccesfully += int64(len(h) + len(sizeBuffer) + len(payload)) + return payload, pageHeader, nil } diff --git a/pkg/media/oggreader/oggreader_test.go b/pkg/media/oggreader/oggreader_test.go index 787f5573b4e..cbb151e9e5f 100644 --- a/pkg/media/oggreader/oggreader_test.go +++ b/pkg/media/oggreader/oggreader_test.go @@ -46,10 +46,12 @@ func TestOggReader_ParseNextPage(t *testing.T) { reader, _, err := NewWith(ogg) assert.NoError(t, err) assert.NotNil(t, reader) + assert.Equal(t, int64(47), reader.bytesReadSuccesfully) payload, _, err := reader.ParseNextPage() assert.Equal(t, []byte{0x98, 0x36, 0xbe, 0x88, 0x9e}, payload) assert.NoError(t, err) + assert.Equal(t, int64(80), reader.bytesReadSuccesfully) _, _, err = reader.ParseNextPage() assert.Equal(t, err, io.EOF) diff --git a/rtpreceiver.go b/rtpreceiver.go index 2457ba080a8..abd2dff969b 100644 --- a/rtpreceiver.go +++ b/rtpreceiver.go @@ -201,7 +201,7 @@ func (r *RTPReceiver) startReceive(parameters RTPReceiveParameters) error { var t *trackStreams for idx, ts := range r.tracks { - if ts.track != nil && parameters.Encodings[i].SSRC != 0 && ts.track.SSRC() == parameters.Encodings[i].SSRC { + if ts.track != nil && ts.track.SSRC() == parameters.Encodings[i].SSRC { t = &r.tracks[idx] break } @@ -210,12 +210,10 @@ func (r *RTPReceiver) startReceive(parameters RTPReceiveParameters) error { return fmt.Errorf("%w: %d", errRTPReceiverWithSSRCTrackStreamNotFound, parameters.Encodings[i].SSRC) } - if parameters.Encodings[i].SSRC != 0 { - t.streamInfo = createStreamInfo("", parameters.Encodings[i].SSRC, 0, codec, globalParams.HeaderExtensions) - var err error - if t.rtpReadStream, t.rtpInterceptor, t.rtcpReadStream, t.rtcpInterceptor, err = r.transport.streamsForSSRC(parameters.Encodings[i].SSRC, *t.streamInfo); err != nil { - return err - } + t.streamInfo = createStreamInfo("", parameters.Encodings[i].SSRC, 0, codec, globalParams.HeaderExtensions) + var err error + if t.rtpReadStream, t.rtpInterceptor, t.rtcpReadStream, t.rtcpInterceptor, err = r.transport.streamsForSSRC(parameters.Encodings[i].SSRC, *t.streamInfo); err != nil { + return err } if rtxSsrc := parameters.Encodings[i].RTX.SSRC; rtxSsrc != 0 { @@ -418,6 +416,10 @@ func (r *RTPReceiver) receiveForRtx(ssrc SSRC, rsid string, streamInfo *intercep for i := range r.tracks { if r.tracks[i].track.RID() == rsid { track = &r.tracks[i] + if track.track.RtxSSRC() == 0 { + track.track.setRtxSSRC(SSRC(streamInfo.SSRC)) + } + break } } } diff --git a/rtpreceiver_go_test.go b/rtpreceiver_go_test.go index 911f0c83822..6a8b2b23c2d 100644 --- a/rtpreceiver_go_test.go +++ b/rtpreceiver_go_test.go @@ -7,11 +7,21 @@ package webrtc import ( + "bufio" "context" + "encoding/binary" + "errors" + "fmt" + "io" + "strconv" + "strings" "testing" "time" + "github.com/pion/randutil" + "github.com/pion/rtp" "github.com/pion/sdp/v3" + "github.com/pion/transport/v3/test" "github.com/pion/webrtc/v4/pkg/media" "github.com/stretchr/testify/assert" ) @@ -70,3 +80,118 @@ func TestSetRTPParameters(t *testing.T) { assert.NoError(t, wan.Stop()) closePairNow(t, sender, receiver) } + +// Assert the behavior of reading a RTX with a distinct SSRC +// All the attributes should be populated and the packet unpacked +func Test_RTX_Read(t *testing.T) { + defer test.TimeOut(time.Second * 30).Stop() + + var ssrc *uint32 + ssrcLines := "" + rtxSsrc := randutil.NewMathRandomGenerator().Uint32() + + pcOffer, pcAnswer, err := newPair() + assert.NoError(t, err) + + track, err := NewTrackLocalStaticRTP(RTPCodecCapability{MimeType: MimeTypeVP8}, "track-id", "stream-id") + assert.NoError(t, err) + + _, err = pcOffer.AddTrack(track) + assert.NoError(t, err) + + rtxRead, rtxReadCancel := context.WithCancel(context.Background()) + pcAnswer.OnTrack(func(track *TrackRemote, _ *RTPReceiver) { + for { + pkt, attributes, readRTPErr := track.ReadRTP() + if errors.Is(readRTPErr, io.EOF) { + return + } else if pkt.PayloadType == 0 { + continue + } + + assert.NoError(t, readRTPErr) + assert.NotNil(t, pkt) + assert.Equal(t, pkt.SSRC, *ssrc) + assert.Equal(t, pkt.PayloadType, uint8(96)) + assert.Equal(t, pkt.Payload, []byte{0xB, 0xA, 0xD}) + + rtxPayloadType := attributes.Get(AttributeRtxPayloadType) + rtxSequenceNumber := attributes.Get(AttributeRtxSequenceNumber) + rtxSSRC := attributes.Get(AttributeRtxSsrc) + if rtxPayloadType != nil && rtxSequenceNumber != nil && rtxSSRC != nil { + assert.Equal(t, rtxPayloadType, uint8(97)) + assert.Equal(t, rtxSSRC, rtxSsrc) + assert.Equal(t, rtxSequenceNumber, pkt.SequenceNumber+500) + + rtxReadCancel() + } + } + }) + + assert.NoError(t, signalPairWithModification(pcOffer, pcAnswer, func(offer string) (modified string) { + scanner := bufio.NewScanner(strings.NewReader(offer)) + for scanner.Scan() { + l := scanner.Text() + + if strings.HasPrefix(l, "a=ssrc") { + if ssrc == nil { + lineSplit := strings.Split(l, " ")[0] + parsed, atoiErr := strconv.ParseUint(strings.TrimPrefix(lineSplit, "a=ssrc:"), 10, 32) + assert.NoError(t, atoiErr) + + parsedSsrc := uint32(parsed) + ssrc = &parsedSsrc + + modified += fmt.Sprintf("a=ssrc-group:FID %d %d\r\n", *ssrc, rtxSsrc) + } + + ssrcLines += l + "\n" + } else if ssrcLines != "" { + ssrcLines = strings.ReplaceAll(ssrcLines, fmt.Sprintf("%d", *ssrc), fmt.Sprintf("%d", rtxSsrc)) + modified += ssrcLines + ssrcLines = "" + } + + modified += l + "\n" + } + + return modified + })) + + func() { + for i := uint16(0); ; i++ { + pkt := rtp.Packet{ + Header: rtp.Header{ + Version: 2, + SSRC: *ssrc, + PayloadType: 96, + SequenceNumber: i, + }, + Payload: []byte{0xB, 0xA, 0xD}, + } + + select { + case <-time.After(20 * time.Millisecond): + // Send the original packet + err = track.WriteRTP(&pkt) + assert.NoError(t, err) + + rtxPayload := []byte{0x0, 0x0, 0xB, 0xA, 0xD} + binary.BigEndian.PutUint16(rtxPayload[0:2], pkt.Header.SequenceNumber) + + // Send the RTX + _, err = track.bindings[0].writeStream.WriteRTP(&rtp.Header{ + Version: 2, + SSRC: rtxSsrc, + PayloadType: 97, + SequenceNumber: i + 500, + }, rtxPayload) + assert.NoError(t, err) + case <-rtxRead.Done(): + return + } + } + }() + + closePairNow(t, pcOffer, pcAnswer) +} diff --git a/rtpsender_test.go b/rtpsender_test.go index cd77976f96e..9b4863ca4aa 100644 --- a/rtpsender_test.go +++ b/rtpsender_test.go @@ -29,10 +29,7 @@ func Test_RTPSender_ReplaceTrack(t *testing.T) { s := SettingEngine{} s.DisableSRTPReplayProtection(true) - m := &MediaEngine{} - assert.NoError(t, m.RegisterDefaultCodecs()) - - sender, receiver, err := NewAPI(WithMediaEngine(m), WithSettingEngine(s)).newPair(Configuration{}) + sender, receiver, err := NewAPI(WithSettingEngine(s)).newPair(Configuration{}) assert.NoError(t, err) trackA, err := NewTrackLocalStaticSample(RTPCodecCapability{MimeType: MimeTypeVP8}, "video", "pion") diff --git a/sdp.go b/sdp.go index 6ea18934710..b66840f274f 100644 --- a/sdp.go +++ b/sdp.go @@ -85,7 +85,7 @@ func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) ( tracksInMediaSection := []trackDetails{} rtxRepairFlows := map[uint64]uint64{} - // Plan B can have multiple tracks in a signle media section + // Plan B can have multiple tracks in a single media section streamID := "" trackID := "" @@ -487,6 +487,11 @@ func addTransceiverSDP( parameters := mediaEngine.getRTPParametersByKind(t.kind, directions) for _, rtpExtension := range parameters.HeaderExtensions { + if mediaSection.matchExtensions != nil { + if _, enabled := mediaSection.matchExtensions[rtpExtension.URI]; !enabled { + continue + } + } extURL, err := url.Parse(rtpExtension.URI) if err != nil { return false, err @@ -533,10 +538,11 @@ type simulcastRid struct { } type mediaSection struct { - id string - transceivers []*RTPTransceiver - data bool - ridMap map[string]*simulcastRid + id string + transceivers []*RTPTransceiver + data bool + matchExtensions map[string]int + ridMap map[string]*simulcastRid } func bundleMatchFromRemote(matchBundleGroup *string) func(mid string) bool { diff --git a/settingengine.go b/settingengine.go index ef0d9bea04e..a0d5e3be1ea 100644 --- a/settingengine.go +++ b/settingengine.go @@ -17,6 +17,7 @@ import ( dtlsElliptic "github.com/pion/dtls/v2/pkg/crypto/elliptic" "github.com/pion/ice/v3" "github.com/pion/logging" + "github.com/pion/stun/v2" "github.com/pion/transport/v3" "github.com/pion/transport/v3/packetio" "golang.org/x/net/proxy" @@ -91,6 +92,7 @@ type SettingEngine struct { iceUDPMux ice.UDPMux iceProxyDialer proxy.Dialer iceDisableActiveTCP bool + iceBindingRequestHandler func(m *stun.Message, local, remote ice.Candidate, pair *ice.CandidatePair) bool disableMediaEngineCopy bool srtpProtectionProfiles []dtls.SRTPProtectionProfile receiveMTU uint @@ -458,3 +460,12 @@ func (e *SettingEngine) SetDTLSCustomerCipherSuites(customCipherSuites func() [] func (e *SettingEngine) SetSCTPRTOMax(rtoMax time.Duration) { e.sctp.rtoMax = rtoMax } + +// SetICEBindingRequestHandler sets a callback that is fired on a STUN BindingRequest +// This allows users to do things like +// - Log incoming Binding Requests for debugging +// - Implement draft-thatcher-ice-renomination +// - Implement custom CandidatePair switching logic +func (e *SettingEngine) SetICEBindingRequestHandler(bindingRequestHandler func(m *stun.Message, local, remote ice.Candidate, pair *ice.CandidatePair) bool) { + e.iceBindingRequestHandler = bindingRequestHandler +} diff --git a/settingengine_test.go b/settingengine_test.go index df88243bbb4..787604b70a5 100644 --- a/settingengine_test.go +++ b/settingengine_test.go @@ -13,6 +13,8 @@ import ( "time" "github.com/pion/dtls/v2/pkg/crypto/elliptic" + "github.com/pion/ice/v3" + "github.com/pion/stun/v2" "github.com/pion/transport/v3/test" "github.com/stretchr/testify/assert" ) @@ -278,3 +280,32 @@ func TestSetSCTPRTOMax(t *testing.T) { s.SetSCTPRTOMax(expSize) assert.Equal(t, expSize, s.sctp.rtoMax) } + +func TestSetICEBindingRequestHandler(t *testing.T) { + seenICEControlled, seenICEControlledCancel := context.WithCancel(context.Background()) + seenICEControlling, seenICEControllingCancel := context.WithCancel(context.Background()) + + s := SettingEngine{} + s.SetICEBindingRequestHandler(func(m *stun.Message, _, _ ice.Candidate, _ *ice.CandidatePair) bool { + for _, a := range m.Attributes { + switch a.Type { + case stun.AttrICEControlled: + seenICEControlledCancel() + case stun.AttrICEControlling: + seenICEControllingCancel() + default: + } + } + + return false + }) + + pcOffer, pcAnswer, err := NewAPI(WithSettingEngine(s)).newPair(Configuration{}) + assert.NoError(t, err) + + assert.NoError(t, signalPair(pcOffer, pcAnswer)) + + <-seenICEControlled.Done() + <-seenICEControlling.Done() + closePairNow(t, pcOffer, pcAnswer) +} diff --git a/test-wasm/node_shim.js b/test-wasm/node_shim.js index 9dcd8016b69..d45ca19bb5a 100644 --- a/test-wasm/node_shim.js +++ b/test-wasm/node_shim.js @@ -4,7 +4,7 @@ // This file adds RTCPeerConnection to the global context, making Node.js more // closely match the browser API for WebRTC. -const wrtc = require('wrtc') +const wrtc = require('@roamhq/wrtc') global.window = { RTCPeerConnection: wrtc.RTCPeerConnection diff --git a/track_remote.go b/track_remote.go index 691b488d83e..7e448dd9895 100644 --- a/track_remote.go +++ b/track_remote.go @@ -224,3 +224,9 @@ func (t *TrackRemote) HasRTX() bool { defer t.mu.RUnlock() return t.rtxSsrc != 0 } + +func (t *TrackRemote) setRtxSSRC(ssrc SSRC) { + t.mu.Lock() + defer t.mu.Unlock() + t.rtxSsrc = ssrc +} diff --git a/yarn.lock b/yarn.lock index 01bf7823dcc..809d55604ca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,10 +4,42 @@ # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +"@roamhq/wrtc-darwin-arm64@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@roamhq/wrtc-darwin-arm64/-/wrtc-darwin-arm64-0.8.0.tgz#15057e6b8f57e4d1b7008a9d848b6f2036adbb24" + integrity sha512-OtV2KWO7zOG3L8TF3KCt9aucynVCD/ww2xeXXgg+FLkya3ca0uzehN8EQJ3BL4tkInksbFJ2ssyu9cehfJ3ZuA== + +"@roamhq/wrtc-darwin-x64@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@roamhq/wrtc-darwin-x64/-/wrtc-darwin-x64-0.8.0.tgz#e09137b5a7edf2c2412bc63f1da1893dcaa211fd" + integrity sha512-VY7Vzt/SDDDCpW//h8GW9bOZrOr8gWXPZVD9473ypl4jyBIoO57yyLbHzd1G0vBUkS6szsHlQCz1WwpI30YL+g== + +"@roamhq/wrtc-linux-arm64@0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@roamhq/wrtc-linux-arm64/-/wrtc-linux-arm64-0.8.1.tgz#9a5f3297de44fcec86713d0baefa0594658ab71e" + integrity sha512-FBJLLazlWkGQUXaokC/rTbrUQbb0CNFYry52fZGstufrGLTWu+g4HcwXdVvxh1tnVtVMvkQGk+mlOL52sCxw0A== + +"@roamhq/wrtc-linux-x64@0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@roamhq/wrtc-linux-x64/-/wrtc-linux-x64-0.8.1.tgz#3c5b60ca6cc6ebf5c2389d852f4a101135031da2" + integrity sha512-I9oWG7b4uvWO1IOR/aF34n+ID6TKVuSs0jd19h5KdhfRtw7FFh9xxuwN9rONPxLVa6fS0q+MCZgAf8Scz89L8Q== + +"@roamhq/wrtc-win32-x64@0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@roamhq/wrtc-win32-x64/-/wrtc-win32-x64-0.8.0.tgz#582e2478df48201d5757b60dcd4b4dcc40a054b7" + integrity sha512-R2fxl41BLWPiP4eaTHGLzbbVvRjx1mV/OsgINCvawO7Hwz5Zx9I45+Fhrw3hd4n5amIeSG9VIF7Kz8eeTFXTGQ== + +"@roamhq/wrtc@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@roamhq/wrtc/-/wrtc-0.8.0.tgz#03c8c64c3b6a1e6e8965ec6496fa7e97571ae04b" + integrity sha512-C0V/nqc4/2xzORI5qa4mIeN/8UO3ywN1kInrJ9u6GljFx0D18JMUJEqe8yYHa61RrEeoWN3PKdW++k8TocSx/A== + optionalDependencies: + "@roamhq/wrtc-darwin-arm64" "0.8.0" + "@roamhq/wrtc-darwin-x64" "0.8.0" + "@roamhq/wrtc-linux-arm64" "0.8.1" + "@roamhq/wrtc-linux-x64" "0.8.1" + "@roamhq/wrtc-win32-x64" "0.8.0" + domexception "^4.0.0" ajv@^6.5.5: version "6.12.2" @@ -19,29 +51,6 @@ ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -69,11 +78,6 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" integrity sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA== -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" @@ -81,29 +85,11 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chownr@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" - integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - combined-stream@^1.0.6, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -111,17 +97,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - -core-util-is@1.0.2, core-util-is@~1.0.0: +core-util-is@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= @@ -133,39 +109,17 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -debug@^2.1.2: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -detect-libc@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - -domexception@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90" - integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug== +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== dependencies: - webidl-conversions "^4.0.2" + webidl-conversions "^7.0.0" ecc-jsbn@~0.1.1: version "0.1.2" @@ -214,32 +168,6 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -fs-minipass@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" - integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== - dependencies: - minipass "^2.2.1" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" @@ -247,18 +175,6 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -glob@^7.1.3: - version "7.1.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" - integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" @@ -272,11 +188,6 @@ har-validator@~5.1.3: ajv "^6.5.5" har-schema "^2.0.0" -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -286,60 +197,11 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -iconv-lite@^0.4.4: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore-walk@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" - integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== - dependencies: - minimatch "^3.0.4" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@~1.3.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" - integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -387,161 +249,16 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.44.0" -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minipass@^2.2.1, minipass@^2.3.4: - version "2.3.5" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" - integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== - dependencies: - safe-buffer "^5.1.2" - yallist "^3.0.0" - -minizlib@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" - integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== - dependencies: - minipass "^2.2.1" - -mkdirp@^0.5.0, mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= - dependencies: - minimist "0.0.8" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -needle@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" - integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== - dependencies: - debug "^2.1.2" - iconv-lite "^0.4.4" - sax "^1.2.4" - -node-pre-gyp@^0.13.0: - version "0.13.0" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.13.0.tgz#df9ab7b68dd6498137717838e4f92a33fc9daa42" - integrity sha512-Md1D3xnEne8b/HGVQkZZwV27WUi1ZRuZBij24TNaZwUPU3ZAFtvT6xxJGaUVillfmMKnn5oD1HoGsp2Ftik7SQ== - dependencies: - detect-libc "^1.0.2" - mkdirp "^0.5.1" - needle "^2.2.1" - nopt "^4.0.1" - npm-packlist "^1.1.6" - npmlog "^4.0.2" - rc "^1.2.7" - rimraf "^2.6.1" - semver "^5.3.0" - tar "^4" - -nopt@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" - integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= - dependencies: - abbrev "1" - osenv "^0.1.4" - -npm-bundled@^1.0.1: - version "1.0.6" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" - integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== - -npm-packlist@^1.1.6: - version "1.4.1" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc" - integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw== - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - -npmlog@^4.0.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== - psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -557,29 +274,6 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -readable-stream@^2.0.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - request@2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" @@ -606,48 +300,21 @@ request@2.88.2: tunnel-agent "^0.6.0" uuid "^3.3.2" -rimraf@^2.6.1: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== - dependencies: - glob "^7.1.3" - safe-buffer@^5.0.1: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" - integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== - -semver@^5.3.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== - -set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -signal-exit@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= - sshpk@^1.7.0: version "1.16.1" resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" @@ -663,62 +330,6 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -tar@^4: - version "4.4.8" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" - integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== - dependencies: - chownr "^1.1.1" - fs-minipass "^1.2.5" - minipass "^2.3.4" - minizlib "^1.1.1" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" - tough-cookie@~2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -746,11 +357,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -765,33 +371,7 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -webidl-conversions@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" - integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg== - -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -wrtc@0.4.7: - version "0.4.7" - resolved "https://registry.yarnpkg.com/wrtc/-/wrtc-0.4.7.tgz#c61530cd662713e50bffe64b7a78673ce070426c" - integrity sha512-P6Hn7VT4lfSH49HxLHcHhDq+aFf/jd9dPY7lDHeFhZ22N3858EKuwm2jmnlPzpsRGEPaoF6XwkcxY5SYnt4f/g== - dependencies: - node-pre-gyp "^0.13.0" - optionalDependencies: - domexception "^1.0.1" - -yallist@^3.0.0, yallist@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" - integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==