diff --git a/apiobject.go b/apiobject.go new file mode 100644 index 00000000..24b4f790 --- /dev/null +++ b/apiobject.go @@ -0,0 +1,19 @@ +package webrtc + +// API is a repository for semi-global settings to WebRTC objects +// In the simplest case, the DefaultAPI object should be used +// rather then constructing a new API object. +type API struct { + settingEngine settingEngine + mediaEngine MediaEngine +} + +var defaultAPI = NewAPI() + +// NewAPI Creates a new API object for keeping semi-global settings to WebRTC objects +func NewAPI() *API { + a := new(API) + initSettingEngine(&a.settingEngine) + InitMediaEngine(&a.mediaEngine) + return a +} diff --git a/mediaengine.go b/mediaengine.go index fd967dfb..a5b46c5d 100644 --- a/mediaengine.go +++ b/mediaengine.go @@ -11,7 +11,7 @@ import ( // RegisterCodec is used to register a codec with the DefaultMediaEngine func RegisterCodec(codec *RTCRtpCodec) { - DefaultMediaEngine.RegisterCodec(codec) + defaultAPI.mediaEngine.RegisterCodec(codec) } // TODO: Phase out DefaultPayloadTypes in favor or dynamic assignment in 96-127 range @@ -26,20 +26,22 @@ const ( ) // RegisterDefaultCodecs is a helper that registers the default codecs supported by pions-webrtc -func RegisterDefaultCodecs() { - RegisterCodec(NewRTCRtpOpusCodec(DefaultPayloadTypeOpus, 48000, 2)) - RegisterCodec(NewRTCRtpG722Codec(DefaultPayloadTypeG722, 8000)) - RegisterCodec(NewRTCRtpVP8Codec(DefaultPayloadTypeVP8, 90000)) - RegisterCodec(NewRTCRtpH264Codec(DefaultPayloadTypeH264, 90000)) - RegisterCodec(NewRTCRtpVP9Codec(DefaultPayloadTypeVP9, 90000)) +func (api *API) RegisterDefaultCodecs() { + api.mediaEngine.RegisterCodec(NewRTCRtpOpusCodec(DefaultPayloadTypeOpus, 48000, 2)) + api.mediaEngine.RegisterCodec(NewRTCRtpG722Codec(DefaultPayloadTypeG722, 8000)) + api.mediaEngine.RegisterCodec(NewRTCRtpVP8Codec(DefaultPayloadTypeVP8, 90000)) + api.mediaEngine.RegisterCodec(NewRTCRtpH264Codec(DefaultPayloadTypeH264, 90000)) + api.mediaEngine.RegisterCodec(NewRTCRtpVP9Codec(DefaultPayloadTypeVP9, 90000)) } -// DefaultMediaEngine is the default MediaEngine used by RTCPeerConnections -var DefaultMediaEngine = NewMediaEngine() +// RegisterDefaultCodecs calls the above on the default api object. +func RegisterDefaultCodecs() { + defaultAPI.RegisterDefaultCodecs() +} -// NewMediaEngine creates a new MediaEngine -func NewMediaEngine() *MediaEngine { - return &MediaEngine{} +// InitMediaEngine initializes an empty media engine object. +func InitMediaEngine(m *MediaEngine) { + *m = MediaEngine{} } // MediaEngine defines the codecs supported by a RTCPeerConnection diff --git a/rtcdatachannel.go b/rtcdatachannel.go index bb74fa7d..82ae8d71 100644 --- a/rtcdatachannel.go +++ b/rtcdatachannel.go @@ -102,7 +102,14 @@ type RTCDataChannel struct { // This constructor is part of the ORTC API. It is not // meant to be used together with the basic WebRTC API. func NewRTCDataChannel(transport *RTCSctpTransport, params *RTCDataChannelParameters) (*RTCDataChannel, error) { - d, err := newRTCDataChannel(params, defaultSettingEngine) + return defaultAPI.NewRTCDataChannel(transport, params) +} + +// NewRTCDataChannel creates a new RTCDataChannel. +// This constructor is part of the ORTC API. It is not +// meant to be used together with the basic WebRTC API. +func (api *API) NewRTCDataChannel(transport *RTCSctpTransport, params *RTCDataChannelParameters) (*RTCDataChannel, error) { + d, err := api.newRTCDataChannel(params) if err != nil { return nil, err } @@ -117,7 +124,7 @@ func NewRTCDataChannel(transport *RTCSctpTransport, params *RTCDataChannelParame // newRTCDataChannel is an internal constructor for the data channel used to // create the RTCDataChannel object before the networking is set up. -func newRTCDataChannel(params *RTCDataChannelParameters, settingEngine *settingEngine) (*RTCDataChannel, error) { +func (api *API) newRTCDataChannel(params *RTCDataChannelParameters) (*RTCDataChannel, error) { // https://w3c.github.io/webrtc-pc/#peer-to-peer-data-api (Step #5) if len(params.Label) > 65535 { return nil, &rtcerr.TypeError{Err: ErrStringSizeLimit} @@ -127,7 +134,7 @@ func newRTCDataChannel(params *RTCDataChannelParameters, settingEngine *settingE Label: params.Label, ID: ¶ms.ID, ReadyState: RTCDataChannelStateConnecting, - settingEngine: settingEngine, + settingEngine: &api.settingEngine, } return d, nil diff --git a/rtcdatachannel_test.go b/rtcdatachannel_test.go index 438e1ea2..2214e8b5 100644 --- a/rtcdatachannel_test.go +++ b/rtcdatachannel_test.go @@ -17,16 +17,16 @@ func TestGenerateDataChannelID(t *testing.T) { c *RTCPeerConnection result uint16 }{ - {true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{}, settingEngine: defaultSettingEngine}, 0}, - {true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil}, settingEngine: defaultSettingEngine}, 0}, - {true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil}, settingEngine: defaultSettingEngine}, 2}, - {true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil, 2: nil}, settingEngine: defaultSettingEngine}, 4}, - {true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil, 4: nil}, settingEngine: defaultSettingEngine}, 2}, - {false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{}, settingEngine: defaultSettingEngine}, 1}, - {false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil}, settingEngine: defaultSettingEngine}, 1}, - {false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil}, settingEngine: defaultSettingEngine}, 3}, - {false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil, 3: nil}, settingEngine: defaultSettingEngine}, 5}, - {false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil, 5: nil}, settingEngine: defaultSettingEngine}, 3}, + {true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{}, api: defaultAPI}, 0}, + {true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil}, api: defaultAPI}, 0}, + {true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil}, api: defaultAPI}, 2}, + {true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil, 2: nil}, api: defaultAPI}, 4}, + {true, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil, 4: nil}, api: defaultAPI}, 2}, + {false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{}, api: defaultAPI}, 1}, + {false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{0: nil}, api: defaultAPI}, 1}, + {false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil}, api: defaultAPI}, 3}, + {false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil, 3: nil}, api: defaultAPI}, 5}, + {false, &RTCPeerConnection{sctpTransport: NewRTCSctpTransport(nil), dataChannels: map[uint16]*RTCDataChannel{1: nil, 5: nil}, api: defaultAPI}, 3}, } for _, testCase := range testCases { @@ -42,7 +42,7 @@ func TestGenerateDataChannelID(t *testing.T) { } func TestRTCDataChannel_EventHandlers(t *testing.T) { - dc := &RTCDataChannel{settingEngine: defaultSettingEngine} + dc := &RTCDataChannel{settingEngine: &defaultAPI.settingEngine} onOpenCalled := make(chan bool) onMessageCalled := make(chan bool) @@ -84,7 +84,7 @@ func TestRTCDataChannel_EventHandlers(t *testing.T) { } func TestRTCDataChannel_MessagesAreOrdered(t *testing.T) { - dc := &RTCDataChannel{settingEngine: defaultSettingEngine} + dc := &RTCDataChannel{settingEngine: &defaultAPI.settingEngine} max := 512 out := make(chan int) diff --git a/rtcicegatherer.go b/rtcicegatherer.go index fddde4da..12de8dfc 100644 --- a/rtcicegatherer.go +++ b/rtcicegatherer.go @@ -25,7 +25,7 @@ type RTCIceGatherer struct { // NewRTCIceGatherer creates a new NewRTCIceGatherer. // This constructor is part of the ORTC API. It is not // meant to be used together with the basic WebRTC API. -func NewRTCIceGatherer(opts RTCIceGatherOptions) (*RTCIceGatherer, error) { +func (api *API) NewRTCIceGatherer(opts RTCIceGatherOptions) (*RTCIceGatherer, error) { validatedServers := []*ice.URL{} if len(opts.ICEServers) > 0 { for _, server := range opts.ICEServers { @@ -40,10 +40,15 @@ func NewRTCIceGatherer(opts RTCIceGatherOptions) (*RTCIceGatherer, error) { return &RTCIceGatherer{ state: RTCIceGathererStateNew, validatedServers: validatedServers, - settingEngine: defaultSettingEngine, + settingEngine: &api.settingEngine, }, nil } +// NewRTCIceGatherer does the same as above, except with the default API object +func NewRTCIceGatherer(opts RTCIceGatherOptions) (*RTCIceGatherer, error) { + return defaultAPI.NewRTCIceGatherer(opts) +} + // State indicates the current state of the ICE gatherer. func (g *RTCIceGatherer) State() RTCIceGathererState { g.lock.RLock() diff --git a/rtcpeerconnection.go b/rtcpeerconnection.go index f820ae17..598025b2 100644 --- a/rtcpeerconnection.go +++ b/rtcpeerconnection.go @@ -96,8 +96,6 @@ type RTCPeerConnection struct { lastOffer string lastAnswer string - // Media - mediaEngine *MediaEngine rtpTransceivers []*RTCRtpTransceiver // DataChannels @@ -126,11 +124,12 @@ type RTCPeerConnection struct { srtcpSession *srtp.SessionSRTCP srtcpEndpoint *mux.Endpoint - // A reference to the associated setting engine used by this peerconnection - settingEngine *settingEngine + // A reference to the associated API state used by this connection + api *API } -func newPC(settings *settingEngine, configuration RTCConfiguration) (*RTCPeerConnection, error) { +// New creates a new RTCPeerConfiguration with the provided configuration against the received API object +func (api *API) New(configuration RTCConfiguration) (*RTCPeerConnection, error) { // 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. @@ -152,11 +151,12 @@ func newPC(settings *settingEngine, configuration RTCConfiguration) (*RTCPeerCon IceConnectionState: ice.ConnectionStateNew, // FIXME REMOVE IceGatheringState: RTCIceGatheringStateNew, ConnectionState: RTCPeerConnectionStateNew, - mediaEngine: DefaultMediaEngine, dataChannels: make(map[uint16]*RTCDataChannel), - srtpSession: srtp.CreateSessionSRTP(), - srtcpSession: srtp.CreateSessionSRTCP(), - settingEngine: settings, + + srtpSession: srtp.CreateSessionSRTP(), + srtcpSession: srtp.CreateSessionSRTCP(), + + api: api, } var err error @@ -182,7 +182,7 @@ func newPC(settings *settingEngine, configuration RTCConfiguration) (*RTCPeerCon // New creates a new RTCPeerConfiguration with the provided configuration func New(configuration RTCConfiguration) (*RTCPeerConnection, error) { - return newPC(defaultSettingEngine, configuration) + return defaultAPI.New(configuration) } // initConfiguration defines validation of the specified RTCConfiguration and @@ -1282,7 +1282,7 @@ func (pc *RTCPeerConnection) CreateDataChannel(label string, options *RTCDataCha } */ - d, err := newRTCDataChannel(params, pc.settingEngine) + d, err := pc.api.newRTCDataChannel(params) if err != nil { return nil, err } @@ -1321,12 +1321,6 @@ func (pc *RTCPeerConnection) generateDataChannelID(client bool) (uint16, error) return 0, &rtcerr.OperationError{Err: ErrMaxDataChannelID} } -// SetMediaEngine allows overwriting the default media engine used by the RTCPeerConnection -// This enables RTCPeerConnection with support for different codecs -func (pc *RTCPeerConnection) SetMediaEngine(m *MediaEngine) { - pc.mediaEngine = m -} - // SetIdentityProvider is used to configure an identity provider to generate identity assertions func (pc *RTCPeerConnection) SetIdentityProvider(provider string) error { return errors.Errorf("TODO SetIdentityProvider") @@ -1448,7 +1442,7 @@ func (pc *RTCPeerConnection) generateChannel(h *rtp.Header) (chan *rtp.Packet, c return nil, nil, fmt.Errorf("no codec could be found in RemoteDescription for payloadType %d", h.PayloadType) } - codec, err := pc.mediaEngine.getCodecSDP(sdpCodec) + codec, err := pc.api.mediaEngine.getCodecSDP(sdpCodec) if err != nil { return nil, nil, fmt.Errorf("codec %s in not registered", sdpCodec) } @@ -1501,7 +1495,7 @@ func (pc *RTCPeerConnection) addFingerprint(d *sdp.SessionDescription) { } func (pc *RTCPeerConnection) addRTPMediaSection(d *sdp.SessionDescription, codecType RTCRtpCodecType, midValue string, iceParams RTCIceParameters, peerDirection RTCRtpTransceiverDirection, candidates []RTCIceCandidate, dtlsRole sdp.ConnectionRole) bool { - if codecs := pc.mediaEngine.getCodecsByKind(codecType); len(codecs) == 0 { + if codecs := pc.api.mediaEngine.getCodecsByKind(codecType); len(codecs) == 0 { return false } media := sdp.NewJSEPMediaDescription(codecType.String(), []string{}). @@ -1511,7 +1505,7 @@ func (pc *RTCPeerConnection) addRTPMediaSection(d *sdp.SessionDescription, codec WithPropertyAttribute(sdp.AttrKeyRtcpMux). // TODO: support RTCP fallback WithPropertyAttribute(sdp.AttrKeyRtcpRsize) // TODO: Support Reduced-Size RTCP? - for _, codec := range pc.mediaEngine.getCodecsByKind(codecType) { + for _, codec := range pc.api.mediaEngine.getCodecsByKind(codecType) { media.WithCodec(codec.PayloadType, codec.Name, codec.ClockRate, codec.Channels, codec.SdpFmtpLine) } @@ -1590,7 +1584,7 @@ func (pc *RTCPeerConnection) sendRTP(packet *rtp.Packet) { } func (pc *RTCPeerConnection) newRTCTrack(payloadType uint8, ssrc uint32, id, label string) (*RTCTrack, error) { - codec, err := pc.mediaEngine.getCodec(payloadType) + codec, err := pc.api.mediaEngine.getCodec(payloadType) if err != nil { return nil, err } else if codec.Payloader == nil { diff --git a/rtcpeerconnection_test.go b/rtcpeerconnection_test.go index e3277cec..f80d34a6 100644 --- a/rtcpeerconnection_test.go +++ b/rtcpeerconnection_test.go @@ -57,6 +57,7 @@ func signalPair(pcOffer *RTCPeerConnection, pcAnswer *RTCPeerConnection) error { } func TestNew(t *testing.T) { + api := NewAPI() t.Run("Success", func(t *testing.T) { secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.Nil(t, err) @@ -64,7 +65,7 @@ func TestNew(t *testing.T) { certificate, err := GenerateCertificate(secretKey) assert.Nil(t, err) - pc, err := New(RTCConfiguration{ + pc, err := api.New(RTCConfiguration{ IceServers: []RTCIceServer{ { URLs: []string{ @@ -106,12 +107,12 @@ func TestNew(t *testing.T) { }) assert.Nil(t, err) - return New(RTCConfiguration{ + return api.New(RTCConfiguration{ Certificates: []RTCCertificate{*certificate}, }) }, &rtcerr.InvalidAccessError{Err: ErrCertificateExpired}}, {func() (*RTCPeerConnection, error) { - return New(RTCConfiguration{ + return api.New(RTCConfiguration{ IceServers: []RTCIceServer{ { URLs: []string{ @@ -135,6 +136,7 @@ func TestNew(t *testing.T) { } func TestRTCPeerConnection_SetConfiguration(t *testing.T) { + api := NewAPI() t.Run("Success", func(t *testing.T) { secretKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) assert.Nil(t, err) @@ -142,7 +144,7 @@ func TestRTCPeerConnection_SetConfiguration(t *testing.T) { certificate, err := GenerateCertificate(secretKey) assert.Nil(t, err) - pc, err := New(RTCConfiguration{ + pc, err := api.New(RTCConfiguration{ PeerIdentity: "unittest", Certificates: []RTCCertificate{*certificate}, IceCandidatePoolSize: 5, @@ -180,7 +182,7 @@ func TestRTCPeerConnection_SetConfiguration(t *testing.T) { expectedErr error }{ {func() (*RTCPeerConnection, error) { - pc, err := New(RTCConfiguration{}) + pc, err := api.New(RTCConfiguration{}) assert.Nil(t, err) err = pc.Close() @@ -190,7 +192,7 @@ func TestRTCPeerConnection_SetConfiguration(t *testing.T) { return RTCConfiguration{} }, &rtcerr.InvalidStateError{Err: ErrConnectionClosed}}, {func() (*RTCPeerConnection, error) { - return New(RTCConfiguration{}) + return api.New(RTCConfiguration{}) }, func() RTCConfiguration { return RTCConfiguration{ PeerIdentity: "unittest", @@ -411,7 +413,8 @@ func TestRTCPeerConnection_NewRTCSampleTrack(t *testing.T) { } func TestRTCPeerConnection_EventHandlers(t *testing.T) { - pc, err := New(RTCConfiguration{}) + api := NewAPI() + pc, err := api.New(RTCConfiguration{}) assert.Nil(t, err) onTrackCalled := make(chan bool) @@ -441,7 +444,7 @@ func TestRTCPeerConnection_EventHandlers(t *testing.T) { // Verify that the set handlers are called assert.NotPanics(t, func() { pc.onTrack(&RTCTrack{}) }) assert.NotPanics(t, func() { pc.onICEConnectionStateChange(ice.ConnectionStateNew) }) - assert.NotPanics(t, func() { go pc.onDataChannelHandler(&RTCDataChannel{settingEngine: defaultSettingEngine}) }) + assert.NotPanics(t, func() { go pc.onDataChannelHandler(&RTCDataChannel{settingEngine: &api.settingEngine}) }) allTrue := func(vals []bool) bool { for _, val := range vals { diff --git a/rtcsctptransport.go b/rtcsctptransport.go index ff508e9a..5c335b0b 100644 --- a/rtcsctptransport.go +++ b/rtcsctptransport.go @@ -35,16 +35,19 @@ type RTCSctpTransport struct { association *sctp.Association onDataChannelHandler func(*RTCDataChannel) + + settingEngine *settingEngine } // NewRTCSctpTransport creates a new RTCSctpTransport. // This constructor is part of the ORTC API. It is not // meant to be used together with the basic WebRTC API. -func NewRTCSctpTransport(dtls *RTCDtlsTransport) *RTCSctpTransport { +func (api *API) NewRTCSctpTransport(dtls *RTCDtlsTransport) *RTCSctpTransport { res := &RTCSctpTransport{ dtlsTransport: dtls, State: RTCSctpTransportStateConnecting, port: 5000, // TODO + settingEngine: &api.settingEngine, } res.updateMessageSize() @@ -53,6 +56,11 @@ func NewRTCSctpTransport(dtls *RTCDtlsTransport) *RTCSctpTransport { return res } +// NewRTCSctpTransport does the same as above, except with the default API object +func NewRTCSctpTransport(dtls *RTCDtlsTransport) *RTCSctpTransport { + return defaultAPI.NewRTCSctpTransport(dtls) +} + // Transport returns the RTCDtlsTransport instance the RTCSctpTransport is sending over. func (r *RTCSctpTransport) Transport() *RTCDtlsTransport { r.lock.RLock() @@ -137,7 +145,7 @@ func (r *RTCSctpTransport) acceptDataChannels() { ID: &sid, Label: dc.Config.Label, ReadyState: RTCDataChannelStateOpen, - settingEngine: defaultSettingEngine, + settingEngine: r.settingEngine, } <-r.onDataChannel(rtcDC) diff --git a/settingengine.go b/settingengine.go index a15db0b9..b909c9cb 100644 --- a/settingengine.go +++ b/settingengine.go @@ -6,26 +6,24 @@ import ( "github.com/pions/webrtc/pkg/ice" ) -var defaultSettingEngine = newSettingEngine() - // SetEphemeralUDPPortRange limits the pool of ephemeral ports that // ICE UDP connections can allocate from. This setting currently only // affects host candidates, not server reflexive candidates. func SetEphemeralUDPPortRange(portMin, portMax uint16) error { - return defaultSettingEngine.SetEphemeralUDPPortRange(portMin, portMax) + return defaultAPI.settingEngine.SetEphemeralUDPPortRange(portMin, portMax) } // DetachDataChannels enables detaching data channels. When enabled // data channels have to be detached in the OnOpen callback using the // RTCDataChannel.Detach method. func DetachDataChannels() { - defaultSettingEngine.DetachDataChannels() + defaultAPI.settingEngine.DetachDataChannels() } // SetConnectionTimeout sets the amount of silence needed on a given candidate pair // before the ICE agent considers the pair timed out. func SetConnectionTimeout(connectionTimeout, keepAlive time.Duration) { - defaultSettingEngine.SetConnectionTimeout(connectionTimeout, keepAlive) + defaultAPI.settingEngine.SetConnectionTimeout(connectionTimeout, keepAlive) } // settingEngine allows influencing behavior in ways that are not @@ -72,6 +70,6 @@ func (e *settingEngine) SetEphemeralUDPPortRange(portMin, portMax uint16) error return nil } -func newSettingEngine() *settingEngine { - return new(settingEngine) +func initSettingEngine(s *settingEngine) { + *s = settingEngine{} }