diff --git a/examples/gstreamer-receive/README.md b/examples/gstreamer-receive/README.md
index ce8ebf57..cd43bb0b 100644
--- a/examples/gstreamer-receive/README.md
+++ b/examples/gstreamer-receive/README.md
@@ -14,7 +14,7 @@ go get github.com/pions/webrtc/examples/gstreamer-receive
```
### Open gstreamer-receive example page
-[jsfiddle.net](https://jsfiddle.net/pdm7bqfr/) you should see your Webcam, two text-areas and a 'Start Session' button
+[jsfiddle.net](https://jsfiddle.net/8t2g5Lar/) you should see your Webcam, two text-areas and a 'Start Session' button
### Run gstreamer-receive with your browsers SessionDescription as stdin
In the jsfiddle the top textarea is your browser, copy that and:
diff --git a/examples/gstreamer-receive/jsfiddle/demo.html b/examples/gstreamer-receive/jsfiddle/demo.html
index fad2a618..7612e161 100644
--- a/examples/gstreamer-receive/jsfiddle/demo.html
+++ b/examples/gstreamer-receive/jsfiddle/demo.html
@@ -4,12 +4,14 @@ Browser base64 Session Description
Golang base64 Session Description
-
+
+
Video
-
+
+
Logs
diff --git a/examples/gstreamer-receive/jsfiddle/demo.js b/examples/gstreamer-receive/jsfiddle/demo.js
index 8599c763..8e185ec0 100644
--- a/examples/gstreamer-receive/jsfiddle/demo.js
+++ b/examples/gstreamer-receive/jsfiddle/demo.js
@@ -7,13 +7,24 @@ let pc = new RTCPeerConnection({
}
]
})
-var log = msg => {
+let log = msg => {
document.getElementById('logs').innerHTML += msg + ' '
}
+let displayVideo = video => {
+ var el = document.createElement('video')
+ el.srcObject = video
+ el.autoplay = true
+ el.muted = true
+ el.width = 160
+ el.height = 120
+
+ document.getElementById('localVideos').appendChild(el)
+ return video
+}
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then(stream => {
- pc.addStream(document.getElementById('video1').srcObject = stream)
+ pc.addStream(displayVideo(stream))
pc.createOffer().then(d => pc.setLocalDescription(d)).catch(log)
}).catch(log)
@@ -36,3 +47,11 @@ window.startSession = () => {
alert(e)
}
}
+
+window.addDisplayCapture = () => {
+ navigator.mediaDevices.getDisplayMedia().then(stream => {
+ document.getElementById('displayCapture').disabled = true
+ pc.addStream(displayVideo(stream))
+ pc.createOffer().then(d => pc.setLocalDescription(d)).catch(log)
+ })
+}
diff --git a/examples/gstreamer-receive/main.go b/examples/gstreamer-receive/main.go
index 6b1d13c3..fa920ea4 100644
--- a/examples/gstreamer-receive/main.go
+++ b/examples/gstreamer-receive/main.go
@@ -32,6 +32,15 @@ func gstreamerReceiveMain() {
panic(err)
}
+ // Allow us to receive 1 audio track, and 2 video tracks
+ if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeAudio); err != nil {
+ panic(err)
+ } else if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeVideo); err != nil {
+ panic(err)
+ } else if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeVideo); err != nil {
+ panic(err)
+ }
+
// Set a handler for when a new remote track starts, this handler creates a gstreamer pipeline
// for the given codec
peerConnection.OnTrack(func(track *webrtc.Track, receiver *webrtc.RTPReceiver) {
diff --git a/examples/gstreamer-send/README.md b/examples/gstreamer-send/README.md
index 894a5c66..edeee01f 100644
--- a/examples/gstreamer-send/README.md
+++ b/examples/gstreamer-send/README.md
@@ -14,7 +14,7 @@ go get github.com/pions/webrtc/examples/gstreamer-send
```
### Open gstreamer-send example page
-[jsfiddle.net](https://jsfiddle.net/Laf7ujeo/164/) you should see two text-areas and a 'Start Session' button
+[jsfiddle.net](https://jsfiddle.net/z7ms3u5r/) you should see two text-areas and a 'Start Session' button
### Run gstreamer-send with your browsers SessionDescription as stdin
In the jsfiddle the top textarea is your browser, copy that and:
diff --git a/examples/gstreamer-send/jsfiddle/demo.js b/examples/gstreamer-send/jsfiddle/demo.js
index 8e0ca2ed..1acb68f3 100644
--- a/examples/gstreamer-send/jsfiddle/demo.js
+++ b/examples/gstreamer-send/jsfiddle/demo.js
@@ -27,7 +27,11 @@ pc.onicecandidate = event => {
}
}
-pc.createOffer({ offerToReceiveVideo: true, offerToReceiveAudio: true }).then(d => pc.setLocalDescription(d)).catch(log)
+// Offer to receive 1 audio, and 2 video tracks
+pc.addTransceiver('audio', {'direction': 'recvonly'})
+pc.addTransceiver('video', {'direction': 'recvonly'})
+pc.addTransceiver('video', {'direction': 'recvonly'})
+pc.createOffer().then(d => pc.setLocalDescription(d)).catch(log)
window.startSession = () => {
let sd = document.getElementById('remoteSessionDescription').value
diff --git a/examples/gstreamer-send/main.go b/examples/gstreamer-send/main.go
index 8cd760ab..858ebcb6 100644
--- a/examples/gstreamer-send/main.go
+++ b/examples/gstreamer-send/main.go
@@ -40,21 +40,31 @@ func main() {
})
// Create a audio track
- opusTrack, err := peerConnection.NewTrack(webrtc.DefaultPayloadTypeOpus, rand.Uint32(), "audio", "pion1")
+ audioTrack, err := peerConnection.NewTrack(webrtc.DefaultPayloadTypeOpus, rand.Uint32(), "audio", "pion1")
if err != nil {
panic(err)
}
- _, err = peerConnection.AddTrack(opusTrack)
+ _, err = peerConnection.AddTrack(audioTrack)
if err != nil {
panic(err)
}
// Create a video track
- vp8Track, err := peerConnection.NewTrack(webrtc.DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
+ firstVideoTrack, err := peerConnection.NewTrack(webrtc.DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
if err != nil {
panic(err)
}
- _, err = peerConnection.AddTrack(vp8Track)
+ _, err = peerConnection.AddTrack(firstVideoTrack)
+ if err != nil {
+ panic(err)
+ }
+
+ // Create a second video track
+ secondVideoTrack, err := peerConnection.NewTrack(webrtc.DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion3")
+ if err != nil {
+ panic(err)
+ }
+ _, err = peerConnection.AddTrack(secondVideoTrack)
if err != nil {
panic(err)
}
@@ -85,8 +95,8 @@ func main() {
fmt.Println(signal.Encode(answer))
// Start pushing buffers on these tracks
- gst.CreatePipeline(webrtc.Opus, []*webrtc.Track{opusTrack}, *audioSrc).Start()
- gst.CreatePipeline(webrtc.VP8, []*webrtc.Track{vp8Track}, *videoSrc).Start()
+ gst.CreatePipeline(webrtc.Opus, []*webrtc.Track{audioTrack}, *audioSrc).Start()
+ gst.CreatePipeline(webrtc.VP8, []*webrtc.Track{firstVideoTrack, secondVideoTrack}, *videoSrc).Start()
// Block forever
select {}
diff --git a/examples/janus-gateway/streaming/main.go b/examples/janus-gateway/streaming/main.go
index 11280609..2ce78d0f 100644
--- a/examples/janus-gateway/streaming/main.go
+++ b/examples/janus-gateway/streaming/main.go
@@ -68,6 +68,13 @@ func main() {
panic(err)
}
+ // Allow us to receive 1 audio track, and 1 video track
+ if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeAudio); err != nil {
+ panic(err)
+ } else if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeVideo); err != nil {
+ panic(err)
+ }
+
peerConnection.OnICEConnectionStateChange(func(connectionState webrtc.ICEConnectionState) {
fmt.Printf("Connection State has changed %s \n", connectionState.String())
})
diff --git a/examples/save-to-disk/main.go b/examples/save-to-disk/main.go
index fbdcb6de..08d655e2 100644
--- a/examples/save-to-disk/main.go
+++ b/examples/save-to-disk/main.go
@@ -61,6 +61,13 @@ func main() {
panic(err)
}
+ // Allow us to receive 1 audio track, and 1 video track
+ if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeAudio); err != nil {
+ panic(err)
+ } else if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeVideo); err != nil {
+ panic(err)
+ }
+
opusFile, err := opuswriter.New("output.opus", 48000, 2)
if err != nil {
panic(err)
diff --git a/examples/sfu-minimal/README.md b/examples/sfu-minimal/README.md
index 9933f67a..a00cd4d1 100644
--- a/examples/sfu-minimal/README.md
+++ b/examples/sfu-minimal/README.md
@@ -10,7 +10,7 @@ go get github.com/pions/webrtc/examples/sfu-minimal
```
### Open sfu-minimal example page
-[jsfiddle.net](https://jsfiddle.net/4g03uqrx/) You should see two buttons 'Publish a Broadcast' and 'Join a Broadcast'
+[jsfiddle.net](https://jsfiddle.net/zhpya3n9/) You should see two buttons 'Publish a Broadcast' and 'Join a Broadcast'
### Run SFU Minimal
#### Linux/macOS
diff --git a/examples/sfu-minimal/jsfiddle/demo.js b/examples/sfu-minimal/jsfiddle/demo.js
index a38ffa6a..25d96de1 100644
--- a/examples/sfu-minimal/jsfiddle/demo.js
+++ b/examples/sfu-minimal/jsfiddle/demo.js
@@ -27,7 +27,8 @@ window.createSession = isPublisher => {
.catch(log)
}).catch(log)
} else {
- pc.createOffer({ offerToReceiveVideo: true })
+ pc.addTransceiver('video', {'direction': 'recvonly'})
+ pc.createOffer()
.then(d => pc.setLocalDescription(d))
.catch(log)
diff --git a/examples/sfu-minimal/main.go b/examples/sfu-minimal/main.go
index b1999eb0..32e63edd 100644
--- a/examples/sfu-minimal/main.go
+++ b/examples/sfu-minimal/main.go
@@ -2,6 +2,7 @@ package main
import (
"fmt"
+ "io"
"time"
"github.com/pions/rtcp"
@@ -46,6 +47,11 @@ func main() {
panic(err)
}
+ // Allow us to receive 1 video track
+ if _, err = peerConnection.AddTransceiver(webrtc.RTPCodecTypeVideo); err != nil {
+ panic(err)
+ }
+
localTrackChan := make(chan *webrtc.Track)
// Set a handler for when a new remote track starts, this just distributes all our packets
// to connected peers
@@ -75,7 +81,8 @@ func main() {
panic(readErr)
}
- if _, err = localTrack.Write(rtpBuf[:i]); err != nil {
+ // ErrClosedPipe means we don't have any subscribers, this is ok if no peers have connected yet
+ if _, err = localTrack.Write(rtpBuf[:i]); err != nil && err != io.ErrClosedPipe {
panic(err)
}
}
diff --git a/examples/sfu-ws/room.go b/examples/sfu-ws/room.go
index 976addf4..f5b8f713 100644
--- a/examples/sfu-ws/room.go
+++ b/examples/sfu-ws/room.go
@@ -1,6 +1,7 @@
package main
import (
+ "io"
"net/http"
"sync"
@@ -69,6 +70,12 @@ func room(w http.ResponseWriter, r *http.Request) {
pubReceiver, err = api.NewPeerConnection(peerConnectionConfig)
checkError(err)
+ _, err = pubReceiver.AddTransceiver(webrtc.RTPCodecTypeAudio)
+ checkError(err)
+
+ _, err = pubReceiver.AddTransceiver(webrtc.RTPCodecTypeVideo)
+ checkError(err)
+
pubReceiver.OnTrack(func(remoteTrack *webrtc.Track, receiver *webrtc.RTPReceiver) {
if remoteTrack.PayloadType() == webrtc.DefaultPayloadTypeVP8 || remoteTrack.PayloadType() == webrtc.DefaultPayloadTypeVP9 || remoteTrack.PayloadType() == webrtc.DefaultPayloadTypeH264 {
@@ -94,7 +101,10 @@ func room(w http.ResponseWriter, r *http.Request) {
videoTrackLock.RLock()
_, err = videoTrack.Write(rtpBuf[:i])
videoTrackLock.RUnlock()
- checkError(err)
+
+ if err != io.ErrClosedPipe {
+ checkError(err)
+ }
}
} else {
@@ -113,7 +123,9 @@ func room(w http.ResponseWriter, r *http.Request) {
audioTrackLock.RLock()
_, err = audioTrack.Write(rtpBuf[:i])
audioTrackLock.RUnlock()
- checkError(err)
+ if err != io.ErrClosedPipe {
+ checkError(err)
+ }
}
}
})
diff --git a/examples/sfu-ws/sfu.html b/examples/sfu-ws/sfu.html
index d8a782ac..a053a4dd 100644
--- a/examples/sfu-ws/sfu.html
+++ b/examples/sfu-ws/sfu.html
@@ -66,7 +66,7 @@ window.sendMessage = element => {
return alert('Message must not be empty')
}
dataChannel.send(message)
- element.value = ''
+ element.value = ''
}
}
@@ -103,7 +103,10 @@ window.createSession = isPublisher => {
document.getElementById('msginput').style = 'display: none'
dataChannel = pc.createDataChannel('data')
dataChannel.onmessage = e => log(`receive data from '${dataChannel.label}' payload '${e.data}'`)
- pc.createOffer({ offerToReceiveVideo: true , offerToReceiveAudio: true})
+ pc.addTransceiver('audio', {'direction': 'recvonly'})
+ pc.addTransceiver('video', {'direction': 'recvonly'})
+
+ pc.createOffer()
.then(d => pc.setLocalDescription(d))
.catch(log)
diff --git a/mediaengine.go b/mediaengine.go
index 63d967b8..0082b38a 100644
--- a/mediaengine.go
+++ b/mediaengine.go
@@ -4,6 +4,7 @@ package webrtc
import (
"strconv"
+ "strings"
"github.com/pions/rtp"
"github.com/pions/rtp/codecs"
@@ -164,6 +165,18 @@ func (t RTPCodecType) String() string {
}
}
+// NewRTPCodecType creates a RTPCodecType from a string
+func NewRTPCodecType(r string) RTPCodecType {
+ switch {
+ case strings.EqualFold(r, "audio"):
+ return RTPCodecTypeAudio
+ case strings.EqualFold(r, "video"):
+ return RTPCodecTypeVideo
+ default:
+ return RTPCodecType(0)
+ }
+}
+
// RTPCodec represents a codec supported by the PeerConnection
type RTPCodec struct {
RTPCodecCapability
diff --git a/peerconnection.go b/peerconnection.go
index ae8a88bb..d7bb6471 100644
--- a/peerconnection.go
+++ b/peerconnection.go
@@ -7,6 +7,8 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
+ mathRand "math/rand"
+
"fmt"
"io"
"net"
@@ -135,7 +137,7 @@ func (api *API) NewPeerConnection(configuration Configuration) (*PeerConnection,
pc.iceTransport = iceTransport
// Create the DTLS transport
- dtlsTransport, err := pc.createDTLSTransport()
+ dtlsTransport, err := pc.api.NewDTLSTransport(pc.iceTransport, pc.configuration.Certificates)
if err != nil {
return nil, err
}
@@ -431,10 +433,6 @@ func (pc *PeerConnection) GetConfiguration() Configuration {
return pc.configuration
}
-// ------------------------------------------------------------------------
-// --- FIXME - BELOW CODE NEEDS REVIEW/CLEANUP
-// ------------------------------------------------------------------------
-
// CreateOffer starts the PeerConnection and generates the localDescription
func (pc *PeerConnection) CreateOffer(options *OfferOptions) (SessionDescription, error) {
useIdentity := pc.idpLoginURL != nil
@@ -461,16 +459,18 @@ func (pc *PeerConnection) CreateOffer(options *OfferOptions) (SessionDescription
}
bundleValue := "BUNDLE"
-
- if pc.addRTPMediaSection(d, RTPCodecTypeAudio, "audio", iceParams, RTPTransceiverDirectionSendrecv, candidates, sdp.ConnectionRoleActpass) {
- bundleValue += " audio"
- }
- if pc.addRTPMediaSection(d, RTPCodecTypeVideo, "video", iceParams, RTPTransceiverDirectionSendrecv, candidates, sdp.ConnectionRoleActpass) {
- bundleValue += " video"
+ bundleCount := 0
+ appendBundle := func() {
+ bundleValue += " " + strconv.Itoa(bundleCount)
+ bundleCount++
}
- pc.addDataMediaSection(d, "data", iceParams, candidates, sdp.ConnectionRoleActpass)
- d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue+" data")
+ for _, t := range pc.GetTransceivers() {
+ pc.addTransceiverSDP(d, t, bundleCount, iceParams, candidates, sdp.ConnectionRoleActpass)
+ appendBundle()
+ }
+ pc.addDataMediaSection(d, bundleCount, iceParams, candidates, sdp.ConnectionRoleActive)
+ appendBundle()
for _, m := range d.MediaDescriptions {
m.WithPropertyAttribute("setup:actpass")
@@ -535,11 +535,6 @@ func (pc *PeerConnection) createICETransport() *ICETransport {
return t
}
-func (pc *PeerConnection) createDTLSTransport() (*DTLSTransport, error) {
- dtlsTransport, err := pc.api.NewDTLSTransport(pc.iceTransport, pc.configuration.Certificates)
- return dtlsTransport, err
-}
-
// CreateAnswer starts the PeerConnection and generates the localDescription
func (pc *PeerConnection) CreateAnswer(options *AnswerOptions) (SessionDescription, error) {
useIdentity := pc.idpLoginURL != nil
@@ -567,43 +562,64 @@ func (pc *PeerConnection) CreateAnswer(options *AnswerOptions) (SessionDescripti
d := sdp.NewJSEPSessionDescription(useIdentity)
pc.addFingerprint(d)
- bundleValue := "BUNDLE"
- for _, remoteMedia := range pc.RemoteDescription().parsed.MediaDescriptions {
- // TODO @trivigy better SDP parser
- var peerDirection RTPTransceiverDirection
- midValue := ""
- for _, a := range remoteMedia.Attributes {
+ getDirection := func(media *sdp.MediaDescription) RTPTransceiverDirection {
+ for _, a := range media.Attributes {
+ if direction := NewRTPTransceiverDirection(a.Key); direction != RTPTransceiverDirection(Unknown) {
+ return direction
+ }
+ }
+ return RTPTransceiverDirection(Unknown)
+ }
+
+ localTransceivers := append([]*RTPTransceiver{}, pc.GetTransceivers()...)
+ satisfyPeerMedia := func(kind RTPCodecType, direction RTPTransceiverDirection) *RTPTransceiver {
+ for i := range localTransceivers {
+ t := localTransceivers[i]
+
switch {
- case strings.HasPrefix(*a.String(), "mid"):
- midValue = (*a.String())[len("mid:"):]
- case strings.HasPrefix(*a.String(), "sendrecv"):
- peerDirection = RTPTransceiverDirectionSendrecv
- case strings.HasPrefix(*a.String(), "sendonly"):
- peerDirection = RTPTransceiverDirectionSendonly
- case strings.HasPrefix(*a.String(), "recvonly"):
- peerDirection = RTPTransceiverDirectionRecvonly
+ case t.kind != kind:
+ continue
+ case direction == RTPTransceiverDirectionSendrecv && t.Direction != RTPTransceiverDirectionSendrecv:
+ continue
+ case direction != RTPTransceiverDirectionSendrecv && direction == t.Direction:
+ continue
+ case direction == RTPTransceiverDirectionInactive:
+ continue
}
+ localTransceivers = append(localTransceivers[:i], localTransceivers[i+1:]...)
+ return t
}
- appendBundle := func() {
- bundleValue += " " + midValue
- }
-
- switch {
- case strings.HasPrefix(*remoteMedia.MediaName.String(), "audio"):
- if pc.addRTPMediaSection(d, RTPCodecTypeAudio, midValue, iceParams, peerDirection, candidates, sdp.ConnectionRoleActive) {
- appendBundle()
- }
- case strings.HasPrefix(*remoteMedia.MediaName.String(), "video"):
- if pc.addRTPMediaSection(d, RTPCodecTypeVideo, midValue, iceParams, peerDirection, candidates, sdp.ConnectionRoleActive) {
- appendBundle()
- }
- case strings.HasPrefix(*remoteMedia.MediaName.String(), "application"):
- pc.addDataMediaSection(d, midValue, iceParams, candidates, sdp.ConnectionRoleActive)
- appendBundle()
+ return &RTPTransceiver{
+ kind: kind,
+ Direction: RTPTransceiverDirectionInactive,
}
}
+ bundleValue := "BUNDLE"
+ bundleCount := 0
+ appendBundle := func() {
+ bundleValue += " " + strconv.Itoa(bundleCount)
+ bundleCount++
+ }
+
+ for _, media := range pc.RemoteDescription().parsed.MediaDescriptions {
+ if media.MediaName.Media == "application" {
+ pc.addDataMediaSection(d, bundleCount, iceParams, candidates, sdp.ConnectionRoleActive)
+ appendBundle()
+ continue
+ }
+
+ kind := NewRTPCodecType(media.MediaName.Media)
+ direction := getDirection(media)
+ if kind == 0 || direction == RTPTransceiverDirection(Unknown) {
+ continue
+ }
+
+ t := satisfyPeerMedia(kind, direction)
+ pc.addTransceiverSDP(d, t, bundleCount, iceParams, candidates, sdp.ConnectionRoleActive)
+ appendBundle()
+ }
d = d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue)
sdp, err := d.Marshal()
@@ -939,13 +955,9 @@ func (pc *PeerConnection) openSRTP() {
for _, media := range pc.RemoteDescription().parsed.MediaDescriptions {
for _, attr := range media.Attributes {
- var codecType RTPCodecType
- switch media.MediaName.Media {
- case "audio":
- codecType = RTPCodecTypeAudio
- case "video":
- codecType = RTPCodecTypeVideo
- default:
+
+ codecType := NewRTPCodecType(media.MediaName.Media)
+ if codecType == 0 {
continue
}
@@ -957,63 +969,68 @@ func (pc *PeerConnection) openSRTP() {
}
incomingSSRCes[uint32(ssrc)] = codecType
+ break
}
}
}
- for i := range incomingSSRCes {
- go func(ssrc uint32, codecType RTPCodecType) {
- receiver, err := pc.api.NewRTPReceiver(codecType, pc.dtlsTransport)
- if err != nil {
- pc.log.Warnf("Could not create RTPReceiver %s", err)
- return
+ localTransceivers := append([]*RTPTransceiver{}, pc.GetTransceivers()...)
+ for ssrc := range incomingSSRCes {
+ for i := range localTransceivers {
+ t := localTransceivers[i]
+ switch {
+ case incomingSSRCes[ssrc] != t.kind:
+ continue
+ case t.Direction != RTPTransceiverDirectionRecvonly && t.Direction != RTPTransceiverDirectionSendrecv:
+ continue
+ case t.Receiver == nil:
+ continue
}
- if err = receiver.Receive(RTPReceiveParameters{
- Encodings: RTPDecodingParameters{
- RTPCodingParameters{SSRC: ssrc},
- }}); err != nil {
- pc.log.Warnf("RTPReceiver Receive failed %s", err)
- return
- }
+ localTransceivers = append(localTransceivers[:i], localTransceivers[i+1:]...)
+ go func(ssrc uint32, receiver *RTPReceiver) {
+ err := receiver.Receive(RTPReceiveParameters{
+ Encodings: RTPDecodingParameters{
+ RTPCodingParameters{SSRC: ssrc},
+ }})
+ if err != nil {
+ pc.log.Warnf("RTPReceiver Receive failed %s", err)
+ return
+ }
- pc.newRTPTransceiver(
- receiver,
- nil,
- RTPTransceiverDirectionRecvonly,
- )
+ if err = receiver.Track().determinePayloadType(); err != nil {
+ pc.log.Warnf("Could not determine PayloadType for SSRC %d", receiver.Track().SSRC())
+ return
+ }
- if err = receiver.Track().determinePayloadType(); err != nil {
- pc.log.Warnf("Could not determine PayloadType for SSRC %d", receiver.Track().SSRC())
- return
- }
+ pc.mu.RLock()
+ defer pc.mu.RUnlock()
- pc.mu.RLock()
- defer pc.mu.RUnlock()
+ sdpCodec, err := pc.currentLocalDescription.parsed.GetCodecForPayloadType(receiver.Track().PayloadType())
+ if err != nil {
+ pc.log.Warnf("no codec could be found in RemoteDescription for payloadType %d", receiver.Track().PayloadType())
+ return
+ }
- sdpCodec, err := pc.currentLocalDescription.parsed.GetCodecForPayloadType(receiver.Track().PayloadType())
- if err != nil {
- pc.log.Warnf("no codec could be found in RemoteDescription for payloadType %d", receiver.Track().PayloadType())
- return
- }
+ codec, err := pc.api.mediaEngine.getCodecSDP(sdpCodec)
+ if err != nil {
+ pc.log.Warnf("codec %s in not registered", sdpCodec)
+ return
+ }
- codec, err := pc.api.mediaEngine.getCodecSDP(sdpCodec)
- if err != nil {
- pc.log.Warnf("codec %s in not registered", sdpCodec)
- return
- }
+ receiver.Track().mu.Lock()
+ receiver.Track().kind = codec.Type
+ receiver.Track().codec = codec
+ receiver.Track().mu.Unlock()
- receiver.Track().mu.Lock()
- receiver.Track().kind = codec.Type
- receiver.Track().codec = codec
- receiver.Track().mu.Unlock()
-
- if pc.onTrackHandler != nil {
- pc.onTrack(receiver.Track(), receiver)
- } else {
- pc.log.Warnf("OnTrack unset, unable to handle incoming media streams")
- }
- }(i, incomingSSRCes[i])
+ if pc.onTrackHandler != nil {
+ pc.onTrack(receiver.Track(), receiver)
+ } else {
+ pc.log.Warnf("OnTrack unset, unable to handle incoming media streams")
+ }
+ }(ssrc, t.Receiver)
+ break
+ }
}
}
@@ -1190,6 +1207,7 @@ func (pc *PeerConnection) AddTrack(track *Track) (*RTPSender, error) {
nil,
sender,
RTPTransceiverDirectionSendonly,
+ track.Kind(),
)
}
@@ -1202,9 +1220,60 @@ func (pc *PeerConnection) AddTrack(track *Track) (*RTPSender, error) {
// panic("not implemented yet") // FIXME NOT-IMPLEMENTED nolint
// }
-// func (pc *PeerConnection) AddTransceiver() RTPTransceiver {
-// panic("not implemented yet") // FIXME NOT-IMPLEMENTED nolint
-// }
+// AddTransceiver Create a new RTCRtpTransceiver and add it to the set of transceivers.
+func (pc *PeerConnection) AddTransceiver(trackOrKind RTPCodecType, init ...RtpTransceiverInit) (*RTPTransceiver, error) {
+ direction := RTPTransceiverDirectionSendrecv
+ if len(init) > 1 {
+ return nil, fmt.Errorf("AddTransceiver only accepts one RtpTransceiverInit")
+ } else if len(init) == 1 {
+ direction = init[0].Direction
+ }
+
+ switch direction {
+ case RTPTransceiverDirectionSendrecv:
+ receiver, err := pc.api.NewRTPReceiver(trackOrKind, pc.dtlsTransport)
+ if err != nil {
+ return nil, err
+ }
+
+ payloadType := DefaultPayloadTypeOpus
+ if trackOrKind == RTPCodecTypeVideo {
+ payloadType = DefaultPayloadTypeVP8
+ }
+
+ track, err := pc.NewTrack(uint8(payloadType), mathRand.Uint32(), util.RandSeq(trackDefaultIDLength), util.RandSeq(trackDefaultLabelLength))
+ if err != nil {
+ return nil, err
+ }
+
+ sender, err := pc.api.NewRTPSender(track, pc.dtlsTransport)
+ if err != nil {
+ return nil, err
+ }
+
+ return pc.newRTPTransceiver(
+ receiver,
+ sender,
+ RTPTransceiverDirectionSendrecv,
+ trackOrKind,
+ ), nil
+
+ case RTPTransceiverDirectionRecvonly:
+ receiver, err := pc.api.NewRTPReceiver(trackOrKind, pc.dtlsTransport)
+ if err != nil {
+ return nil, err
+ }
+
+ return pc.newRTPTransceiver(
+ receiver,
+ nil,
+ RTPTransceiverDirectionRecvonly,
+ trackOrKind,
+ ), nil
+ default:
+ return nil, fmt.Errorf("AddTransceiver currently only suports recvonly and sendrecv")
+ }
+}
// CreateDataChannel creates a new DataChannel object with the given label
// and optional DataChannelInit used to configure properties of the
@@ -1406,20 +1475,6 @@ func (pc *PeerConnection) iceStateChange(newState ICEConnectionState) {
pc.onICEConnectionStateChange(newState)
}
-func localDirection(weSend bool, peerDirection RTPTransceiverDirection) RTPTransceiverDirection {
- theySend := (peerDirection == RTPTransceiverDirectionSendrecv || peerDirection == RTPTransceiverDirectionSendonly)
- switch {
- case weSend && theySend:
- return RTPTransceiverDirectionSendrecv
- case weSend && !theySend:
- return RTPTransceiverDirectionSendonly
- case !weSend && theySend:
- return RTPTransceiverDirectionRecvonly
- }
-
- return RTPTransceiverDirectionInactive
-}
-
func (pc *PeerConnection) addFingerprint(d *sdp.SessionDescription) {
// TODO: Handle multiple certificates
for _, fingerprint := range pc.configuration.Certificates[0].GetFingerprints() {
@@ -1427,26 +1482,15 @@ func (pc *PeerConnection) addFingerprint(d *sdp.SessionDescription) {
}
}
-func (pc *PeerConnection) addRTPMediaSection(d *sdp.SessionDescription, codecType RTPCodecType, midValue string, iceParams ICEParameters, peerDirection RTPTransceiverDirection, candidates []ICECandidate, dtlsRole sdp.ConnectionRole) bool {
- if codecs := pc.api.mediaEngine.getCodecsByKind(codecType); len(codecs) == 0 {
- d.WithMedia(&sdp.MediaDescription{
- MediaName: sdp.MediaName{
- Media: codecType.String(),
- Port: sdp.RangedPort{Value: 0},
- Protos: []string{"UDP", "TLS", "RTP", "SAVPF"},
- Formats: []string{"0"},
- },
- })
- return false
- }
- media := sdp.NewJSEPMediaDescription(codecType.String(), []string{}).
+func (pc *PeerConnection) addTransceiverSDP(d *sdp.SessionDescription, t *RTPTransceiver, midOffset int, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole) {
+ media := sdp.NewJSEPMediaDescription(t.kind.String(), []string{}).
WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). // TODO: Support other connection types
- WithValueAttribute(sdp.AttrKeyMID, midValue).
+ WithValueAttribute(sdp.AttrKeyMID, strconv.Itoa(midOffset)).
WithICECredentials(iceParams.UsernameFragment, iceParams.Password).
WithPropertyAttribute(sdp.AttrKeyRTCPMux). // TODO: support RTCP fallback
WithPropertyAttribute(sdp.AttrKeyRTCPRsize) // TODO: Support Reduced-Size RTCP?
- for _, codec := range pc.api.mediaEngine.getCodecsByKind(codecType) {
+ for _, codec := range pc.api.mediaEngine.getCodecsByKind(t.kind) {
media.WithCodec(codec.PayloadType, codec.Name, codec.ClockRate, codec.Channels, codec.SDPFmtpLine)
for _, feedback := range codec.RTPCodecCapability.RTCPFeedback {
@@ -1454,19 +1498,13 @@ func (pc *PeerConnection) addRTPMediaSection(d *sdp.SessionDescription, codecTyp
}
}
- weSend := false
- for _, transceiver := range pc.rtpTransceivers {
- if transceiver.Sender == nil ||
- transceiver.Sender.track == nil ||
- transceiver.Sender.track.Kind() != codecType {
- continue
- }
- weSend = true
- track := transceiver.Sender.track
- media = media.WithMediaSource(track.SSRC(), track.Label() /* cname */, track.Label() /* streamLabel */, track.Label())
+ if t.Sender != nil && t.Sender.track != nil {
+ track := t.Sender.track
+ media = media.WithPropertyAttribute("msid:" + track.Label() + " " + track.ID())
+ media = media.WithMediaSource(track.SSRC(), track.Label() /* cname */, track.Label() /* streamLabel */, track.ID())
}
- media = media.WithPropertyAttribute(localDirection(weSend, peerDirection).String())
+ media = media.WithPropertyAttribute(t.Direction.String())
for _, c := range candidates {
sdpCandidate := c.toSDP()
sdpCandidate.ExtensionAttributes = append(sdpCandidate.ExtensionAttributes, sdp.ICECandidateAttribute{Key: "generation", Value: "0"})
@@ -1475,12 +1513,14 @@ func (pc *PeerConnection) addRTPMediaSection(d *sdp.SessionDescription, codecTyp
sdpCandidate.Component = 2
media.WithICECandidate(sdpCandidate)
}
- media.WithPropertyAttribute("end-of-candidates")
+ if len(candidates) != 0 {
+ media.WithPropertyAttribute("end-of-candidates")
+ }
+
d.WithMedia(media)
- return true
}
-func (pc *PeerConnection) addDataMediaSection(d *sdp.SessionDescription, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole) {
+func (pc *PeerConnection) addDataMediaSection(d *sdp.SessionDescription, midOffset int, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole) {
media := (&sdp.MediaDescription{
MediaName: sdp.MediaName{
Media: "application",
@@ -1497,7 +1537,7 @@ func (pc *PeerConnection) addDataMediaSection(d *sdp.SessionDescription, midValu
},
}).
WithValueAttribute(sdp.AttrKeyConnectionSetup, dtlsRole.String()). // TODO: Support other connection types
- WithValueAttribute(sdp.AttrKeyMID, midValue).
+ WithValueAttribute(sdp.AttrKeyMID, strconv.Itoa(midOffset)).
WithPropertyAttribute(RTPTransceiverDirectionSendrecv.String()).
WithPropertyAttribute("sctpmap:5000 webrtc-datachannel 1024").
WithICECredentials(iceParams.UsernameFragment, iceParams.Password)
@@ -1531,12 +1571,14 @@ func (pc *PeerConnection) newRTPTransceiver(
receiver *RTPReceiver,
sender *RTPSender,
direction RTPTransceiverDirection,
+ kind RTPCodecType,
) *RTPTransceiver {
t := &RTPTransceiver{
Receiver: receiver,
Sender: sender,
Direction: direction,
+ kind: kind,
}
pc.mu.Lock()
defer pc.mu.Unlock()
diff --git a/peerconnection_media_test.go b/peerconnection_media_test.go
index 89a739c0..d15518ba 100644
--- a/peerconnection_media_test.go
+++ b/peerconnection_media_test.go
@@ -37,6 +37,11 @@ func TestPeerConnection_Media_Sample(t *testing.T) {
t.Fatal(err)
}
+ _, err = pcAnswer.AddTransceiver(RTPCodecTypeVideo)
+ if err != nil {
+ t.Fatal(err)
+ }
+
awaitRTPRecv := make(chan bool)
awaitRTPRecvClosed := make(chan bool)
awaitRTPSend := make(chan bool)
@@ -191,6 +196,16 @@ func TestPeerConnection_Media_Shutdown(t *testing.T) {
t.Fatal(err)
}
+ _, err = pcOffer.AddTransceiver(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, err = pcAnswer.AddTransceiver(RTPCodecTypeVideo, RtpTransceiverInit{Direction: RTPTransceiverDirectionRecvonly})
+ if err != nil {
+ t.Fatal(err)
+ }
+
opusTrack, err := pcOffer.NewTrack(DefaultPayloadTypeOpus, rand.Uint32(), "audio", "pion1")
if err != nil {
t.Fatal(err)
@@ -366,6 +381,11 @@ func TestPeerConnection_Media_Closed(t *testing.T) {
t.Fatal(err)
}
+ _, err = pcAnswer.AddTransceiver(RTPCodecTypeVideo)
+ if err != nil {
+ t.Fatal(err)
+ }
+
vp8Writer, err := pcOffer.NewTrack(DefaultPayloadTypeVP8, rand.Uint32(), "video", "pion2")
if err != nil {
t.Fatal(err)
diff --git a/rtptransceiver.go b/rtptransceiver.go
index 29157bf5..df5fd5c9 100644
--- a/rtptransceiver.go
+++ b/rtptransceiver.go
@@ -2,7 +2,9 @@
package webrtc
-import "fmt"
+import (
+ "fmt"
+)
// RTPTransceiver represents a combination of an RTPSender and an RTPReceiver that share a common mid.
type RTPTransceiver struct {
@@ -14,6 +16,7 @@ type RTPTransceiver struct {
// firedDirection RTPTransceiverDirection
// receptive bool
stopped bool
+ kind RTPCodecType
}
func (t *RTPTransceiver) setSendingTrack(track *Track) error {
diff --git a/rtptransceiverinit.go b/rtptransceiverinit.go
new file mode 100644
index 00000000..19c1769d
--- /dev/null
+++ b/rtptransceiverinit.go
@@ -0,0 +1,8 @@
+package webrtc
+
+// RtpTransceiverInit dictionary is used when calling the WebRTC function addTransceiver() to provide configuration options for the new transceiver.
+type RtpTransceiverInit struct {
+ Direction RTPTransceiverDirection
+ SendEncodings []RTPEncodingParameters
+ // Streams []*Track
+}
diff --git a/track.go b/track.go
index cb026f02..98e8f047 100644
--- a/track.go
+++ b/track.go
@@ -11,7 +11,11 @@ import (
"github.com/pions/webrtc/pkg/media"
)
-const rtpOutboundMTU = 1400
+const (
+ rtpOutboundMTU = 1400
+ trackDefaultIDLength = 16
+ trackDefaultLabelLength = 16
+)
// Track represents a single media track
type Track struct {