mirror of
https://github.com/pion/webrtc.git
synced 2025-10-29 01:42:59 +08:00
Add extmap support
Extmaps are configured via the SettingEngine. This allows a user to set arbitrary values, and when answering ids and entries are properly excluded. Co-authored-by: Gabor Pongracz <gabor.pongracz@proemergotech.com>
This commit is contained in:
@@ -178,6 +178,7 @@ Check out the **[contributing wiki](https://github.com/pion/webrtc/wiki/Contribu
|
|||||||
* [Vitaliy F](https://github.com/funvit)
|
* [Vitaliy F](https://github.com/funvit)
|
||||||
* [Ivan Egorov](https://github.com/vany-egorov)
|
* [Ivan Egorov](https://github.com/vany-egorov)
|
||||||
* [Nick Mykins](https://github.com/nmyk)
|
* [Nick Mykins](https://github.com/nmyk)
|
||||||
|
* [Jason Brady](https://github.com/jbrady42)
|
||||||
|
|
||||||
### License
|
### License
|
||||||
MIT License - see [LICENSE](LICENSE) for full text
|
MIT License - see [LICENSE](LICENSE) for full text
|
||||||
|
|||||||
@@ -1787,7 +1787,7 @@ func (pc *PeerConnection) generateUnmatchedSDP(useIdentity bool) (*sdp.SessionDe
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return populateSDP(d, isPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, pc.api.mediaEngine, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), candidates, iceParams, mediaSections, pc.ICEGatheringState())
|
return populateSDP(d, isPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, pc.api.mediaEngine, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), candidates, iceParams, mediaSections, pc.ICEGatheringState(), pc.api.settingEngine.getSDPExtensions())
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateMatchedSDP generates a SDP and takes the remote state into account
|
// generateMatchedSDP generates a SDP and takes the remote state into account
|
||||||
@@ -1889,7 +1889,12 @@ func (pc *PeerConnection) generateMatchedSDP(useIdentity bool, includeUnmatched
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return populateSDP(d, detectedPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, pc.api.mediaEngine, connectionRole, candidates, iceParams, mediaSections, pc.ICEGatheringState())
|
matchedSDPMap, err := matchedAnswerExt(pc.RemoteDescription().parsed, pc.api.settingEngine.getSDPExtensions())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return populateSDP(d, detectedPlanB, dtlsFingerprints, pc.api.settingEngine.sdpMediaLevelFingerprints, pc.api.settingEngine.candidates.ICELite, pc.api.mediaEngine, connectionRole, candidates, iceParams, mediaSections, pc.ICEGatheringState(), matchedSDPMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc *PeerConnection) setGatherCompleteHdlr(hdlr func()) {
|
func (pc *PeerConnection) setGatherCompleteHdlr(hdlr func()) {
|
||||||
|
|||||||
104
sdp.go
104
sdp.go
@@ -20,6 +20,16 @@ type trackDetails struct {
|
|||||||
ssrc uint32
|
ssrc uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SDPSectionType specifies media type sections
|
||||||
|
type SDPSectionType string
|
||||||
|
|
||||||
|
// Common SDP sections
|
||||||
|
const (
|
||||||
|
SDPSectionGlobal = SDPSectionType("global")
|
||||||
|
SDPSectionVideo = SDPSectionType("video")
|
||||||
|
SDPSectionAudio = SDPSectionType("audio")
|
||||||
|
)
|
||||||
|
|
||||||
// extract all trackDetails from an SDP.
|
// extract all trackDetails from an SDP.
|
||||||
func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) map[uint32]trackDetails {
|
func trackDetailsFromSDP(log logging.LeveledLogger, s *sdp.SessionDescription) map[uint32]trackDetails {
|
||||||
incomingTracks := map[uint32]trackDetails{}
|
incomingTracks := map[uint32]trackDetails{}
|
||||||
@@ -200,7 +210,7 @@ func populateLocalCandidates(sessionDescription *SessionDescription, i *ICEGathe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTransceiverSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTLSFingerprint, mediaEngine *MediaEngine, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState, transceivers ...*RTPTransceiver) (bool, error) {
|
func addTransceiverSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTLSFingerprint, mediaEngine *MediaEngine, midValue string, iceParams ICEParameters, candidates []ICECandidate, dtlsRole sdp.ConnectionRole, iceGatheringState ICEGatheringState, extMaps map[SDPSectionType][]sdp.ExtMap, transceivers ...*RTPTransceiver) (bool, error) {
|
||||||
if len(transceivers) < 1 {
|
if len(transceivers) < 1 {
|
||||||
return false, fmt.Errorf("addTransceiverSDP() called with 0 transceivers")
|
return false, fmt.Errorf("addTransceiverSDP() called with 0 transceivers")
|
||||||
}
|
}
|
||||||
@@ -219,9 +229,6 @@ func addTransceiverSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints
|
|||||||
|
|
||||||
for _, feedback := range codec.RTPCodecCapability.RTCPFeedback {
|
for _, feedback := range codec.RTPCodecCapability.RTCPFeedback {
|
||||||
media.WithValueAttribute("rtcp-fb", fmt.Sprintf("%d %s %s", codec.PayloadType, feedback.Type, feedback.Parameter))
|
media.WithValueAttribute("rtcp-fb", fmt.Sprintf("%d %s %s", codec.PayloadType, feedback.Type, feedback.Parameter))
|
||||||
if feedback.Type == TypeRTCPFBTransportCC {
|
|
||||||
media.WithTransportCCExtMap()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(codecs) == 0 {
|
if len(codecs) == 0 {
|
||||||
@@ -237,6 +244,13 @@ func addTransceiverSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add extmaps
|
||||||
|
if maps, ok := extMaps[SDPSectionType(t.kind.String())]; ok {
|
||||||
|
for _, m := range maps {
|
||||||
|
media.WithExtMap(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, mt := range transceivers {
|
for _, mt := range transceivers {
|
||||||
if mt.Sender() != nil && mt.Sender().track != nil {
|
if mt.Sender() != nil && mt.Sender().track != nil {
|
||||||
track := mt.Sender().track
|
track := mt.Sender().track
|
||||||
@@ -267,7 +281,7 @@ type mediaSection struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// populateSDP serializes a PeerConnections state into an SDP
|
// populateSDP serializes a PeerConnections state into an SDP
|
||||||
func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTLSFingerprint, mediaDescriptionFingerprint bool, isICELite bool, mediaEngine *MediaEngine, connectionRole sdp.ConnectionRole, candidates []ICECandidate, iceParams ICEParameters, mediaSections []mediaSection, iceGatheringState ICEGatheringState) (*sdp.SessionDescription, error) {
|
func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTLSFingerprint, mediaDescriptionFingerprint bool, isICELite bool, mediaEngine *MediaEngine, connectionRole sdp.ConnectionRole, candidates []ICECandidate, iceParams ICEParameters, mediaSections []mediaSection, iceGatheringState ICEGatheringState, extMaps map[SDPSectionType][]sdp.ExtMap) (*sdp.SessionDescription, error) {
|
||||||
var err error
|
var err error
|
||||||
mediaDtlsFingerprints := []DTLSFingerprint{}
|
mediaDtlsFingerprints := []DTLSFingerprint{}
|
||||||
|
|
||||||
@@ -293,7 +307,7 @@ func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTL
|
|||||||
if m.data {
|
if m.data {
|
||||||
addDataMediaSection(d, mediaDtlsFingerprints, m.id, iceParams, candidates, connectionRole, iceGatheringState)
|
addDataMediaSection(d, mediaDtlsFingerprints, m.id, iceParams, candidates, connectionRole, iceGatheringState)
|
||||||
} else {
|
} else {
|
||||||
shouldAddID, err = addTransceiverSDP(d, isPlanB, mediaDtlsFingerprints, mediaEngine, m.id, iceParams, candidates, connectionRole, iceGatheringState, m.transceivers...)
|
shouldAddID, err = addTransceiverSDP(d, isPlanB, mediaDtlsFingerprints, mediaEngine, m.id, iceParams, candidates, connectionRole, iceGatheringState, extMaps, m.transceivers...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -314,6 +328,14 @@ func populateSDP(d *sdp.SessionDescription, isPlanB bool, dtlsFingerprints []DTL
|
|||||||
// RFC 5245 S15.3
|
// RFC 5245 S15.3
|
||||||
d = d.WithValueAttribute(sdp.AttrKeyICELite, sdp.AttrKeyICELite)
|
d = d.WithValueAttribute(sdp.AttrKeyICELite, sdp.AttrKeyICELite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add global exts
|
||||||
|
if maps, ok := extMaps[SDPSectionGlobal]; ok {
|
||||||
|
for _, m := range maps {
|
||||||
|
d.WithPropertyAttribute(m.Marshal())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue), nil
|
return d.WithValueAttribute(sdp.AttrKeyGroup, bundleValue), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,3 +468,73 @@ func haveApplicationMediaSection(desc *sdp.SessionDescription) bool {
|
|||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func matchedAnswerExt(descriptions *sdp.SessionDescription, localMaps map[SDPSectionType][]sdp.ExtMap) (map[SDPSectionType][]sdp.ExtMap, error) {
|
||||||
|
remoteExtMaps, err := remoteExts(descriptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return answerExtMaps(remoteExtMaps, localMaps), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func answerExtMaps(remoteExtMaps map[SDPSectionType]map[int]sdp.ExtMap, localMaps map[SDPSectionType][]sdp.ExtMap) map[SDPSectionType][]sdp.ExtMap {
|
||||||
|
ret := map[SDPSectionType][]sdp.ExtMap{}
|
||||||
|
for mediaType, remoteExtMap := range remoteExtMaps {
|
||||||
|
if _, ok := ret[mediaType]; !ok {
|
||||||
|
ret[mediaType] = []sdp.ExtMap{}
|
||||||
|
}
|
||||||
|
for _, extItem := range remoteExtMap {
|
||||||
|
// add remote ext that match locally available ones
|
||||||
|
for _, extMap := range localMaps[mediaType] {
|
||||||
|
if extMap.URI.String() == extItem.URI.String() {
|
||||||
|
ret[mediaType] = append(ret[mediaType], extItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func remoteExts(session *sdp.SessionDescription) (map[SDPSectionType]map[int]sdp.ExtMap, error) {
|
||||||
|
remoteExtMaps := map[SDPSectionType]map[int]sdp.ExtMap{}
|
||||||
|
|
||||||
|
maybeAddExt := func(attr sdp.Attribute, mediaType SDPSectionType) error {
|
||||||
|
if attr.Key != "extmap" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
em := &sdp.ExtMap{}
|
||||||
|
if err := em.Unmarshal("extmap:" + attr.Value); err != nil {
|
||||||
|
return fmt.Errorf("failed to parse ExtMap: %v", err)
|
||||||
|
}
|
||||||
|
if remoteExtMap, ok := remoteExtMaps[mediaType][em.Value]; ok {
|
||||||
|
if remoteExtMap.Value != em.Value {
|
||||||
|
return fmt.Errorf("RemoteDescription changed some extmaps values")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
remoteExtMaps[mediaType][em.Value] = *em
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate the extmaps from the current remote description
|
||||||
|
for _, media := range session.MediaDescriptions {
|
||||||
|
mediaType := SDPSectionType(media.MediaName.Media)
|
||||||
|
// populate known remote extmap and handle conflicts.
|
||||||
|
if _, ok := remoteExtMaps[mediaType]; !ok {
|
||||||
|
remoteExtMaps[mediaType] = map[int]sdp.ExtMap{}
|
||||||
|
}
|
||||||
|
for _, attr := range media.Attributes {
|
||||||
|
if err := maybeAddExt(attr, mediaType); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add global exts
|
||||||
|
for _, attr := range session.Attributes {
|
||||||
|
if err := maybeAddExt(attr, SDPSectionGlobal); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return remoteExtMaps, nil
|
||||||
|
}
|
||||||
|
|||||||
115
sdp_test.go
115
sdp_test.go
@@ -6,6 +6,7 @@ import (
|
|||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -340,7 +341,7 @@ func TestMediaDescriptionFingerprints(t *testing.T) {
|
|||||||
s, err = populateSDP(s, false,
|
s, err = populateSDP(s, false,
|
||||||
dtlsFingerprints,
|
dtlsFingerprints,
|
||||||
SDPMediaDescriptionFingerprints,
|
SDPMediaDescriptionFingerprints,
|
||||||
false, engine, sdp.ConnectionRoleActive, []ICECandidate{}, ICEParameters{}, media, ICEGatheringStateNew)
|
false, engine, sdp.ConnectionRoleActive, []ICECandidate{}, ICEParameters{}, media, ICEGatheringStateNew, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
sdparray, err := s.Marshal()
|
sdparray, err := s.Marshal()
|
||||||
@@ -353,3 +354,115 @@ func TestMediaDescriptionFingerprints(t *testing.T) {
|
|||||||
t.Run("Per-Media Description Fingerprints", fingerprintTest(true, 3))
|
t.Run("Per-Media Description Fingerprints", fingerprintTest(true, 3))
|
||||||
t.Run("Per-Session Description Fingerprints", fingerprintTest(false, 1))
|
t.Run("Per-Session Description Fingerprints", fingerprintTest(false, 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPopulateSDP(t *testing.T) {
|
||||||
|
t.Run("Offer", func(t *testing.T) {
|
||||||
|
transportCCURL, _ := url.Parse(sdp.TransportCCURI)
|
||||||
|
absSendURL, _ := url.Parse(sdp.ABSSendTimeURI)
|
||||||
|
|
||||||
|
globalExts := []sdp.ExtMap{
|
||||||
|
{
|
||||||
|
URI: transportCCURL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
videoExts := []sdp.ExtMap{
|
||||||
|
{
|
||||||
|
URI: absSendURL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tr := &RTPTransceiver{kind: RTPCodecTypeVideo}
|
||||||
|
tr.setDirection(RTPTransceiverDirectionSendrecv)
|
||||||
|
mediaSections := []mediaSection{{id: "video", transceivers: []*RTPTransceiver{tr}}}
|
||||||
|
|
||||||
|
se := SettingEngine{}
|
||||||
|
se.AddSDPExtensions(SDPSectionGlobal, globalExts)
|
||||||
|
se.AddSDPExtensions(SDPSectionVideo, videoExts)
|
||||||
|
|
||||||
|
m := MediaEngine{}
|
||||||
|
m.RegisterDefaultCodecs()
|
||||||
|
|
||||||
|
d := &sdp.SessionDescription{}
|
||||||
|
|
||||||
|
offerSdp, err := populateSDP(d, false, []DTLSFingerprint{}, se.sdpMediaLevelFingerprints, se.candidates.ICELite, &m, connectionRoleFromDtlsRole(defaultDtlsRoleOffer), []ICECandidate{}, ICEParameters{}, mediaSections, ICEGatheringStateComplete, se.getSDPExtensions())
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
// Check global extensions
|
||||||
|
var found bool
|
||||||
|
for _, a := range offerSdp.Attributes {
|
||||||
|
if strings.Contains(a.Key, transportCCURL.String()) {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Equal(t, true, found, "Global extension should be present")
|
||||||
|
|
||||||
|
// Check video extension
|
||||||
|
found = false
|
||||||
|
var foundGlobal bool
|
||||||
|
for _, desc := range offerSdp.MediaDescriptions {
|
||||||
|
if desc.MediaName.Media != mediaNameVideo {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, a := range desc.Attributes {
|
||||||
|
if strings.Contains(a.Key, absSendURL.String()) {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
if strings.Contains(a.Key, transportCCURL.String()) {
|
||||||
|
foundGlobal = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.Equal(t, true, found, "Video extension should be present")
|
||||||
|
// Test video does not contain global
|
||||||
|
assert.Equal(t, false, foundGlobal, "Global extension should not be present in video section")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMatchedAnswerExt(t *testing.T) {
|
||||||
|
s := &sdp.SessionDescription{
|
||||||
|
MediaDescriptions: []*sdp.MediaDescription{
|
||||||
|
{
|
||||||
|
MediaName: sdp.MediaName{
|
||||||
|
Media: "video",
|
||||||
|
},
|
||||||
|
Attributes: []sdp.Attribute{
|
||||||
|
{Key: "sendrecv"},
|
||||||
|
{Key: "ssrc", Value: "2000"},
|
||||||
|
{Key: "extmap", Value: "5 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
transportCCURL, _ := url.Parse(sdp.TransportCCURI)
|
||||||
|
absSendURL, _ := url.Parse(sdp.ABSSendTimeURI)
|
||||||
|
|
||||||
|
exts := []sdp.ExtMap{
|
||||||
|
{
|
||||||
|
URI: transportCCURL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URI: absSendURL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
se := SettingEngine{}
|
||||||
|
se.AddSDPExtensions(SDPSectionVideo, exts)
|
||||||
|
|
||||||
|
ansMaps, err := matchedAnswerExt(s, se.getSDPExtensions())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Ext parse error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if maps := ansMaps[SDPSectionVideo]; maps != nil {
|
||||||
|
// Check answer contains intersect of remote and local
|
||||||
|
// Abs send time should be only extenstion
|
||||||
|
assert.Equal(t, 1, len(maps), "Only one extension should be active")
|
||||||
|
assert.Equal(t, absSendURL, maps[0].URI, "Only abs-send-time should be active")
|
||||||
|
|
||||||
|
// Check answer uses remote IDs
|
||||||
|
assert.Equal(t, 5, maps[0].Value, "Should use remote ext ID")
|
||||||
|
} else {
|
||||||
|
t.Fatal("No video ext maps found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/pion/ice/v2"
|
"github.com/pion/ice/v2"
|
||||||
"github.com/pion/logging"
|
"github.com/pion/logging"
|
||||||
|
"github.com/pion/sdp/v2"
|
||||||
"github.com/pion/transport/vnet"
|
"github.com/pion/transport/vnet"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,6 +50,7 @@ type SettingEngine struct {
|
|||||||
SRTCP *uint
|
SRTCP *uint
|
||||||
}
|
}
|
||||||
sdpMediaLevelFingerprints bool
|
sdpMediaLevelFingerprints bool
|
||||||
|
sdpExtensions map[SDPSectionType][]sdp.ExtMap
|
||||||
answeringDTLSRole DTLSRole
|
answeringDTLSRole DTLSRole
|
||||||
disableCertificateFingerprintVerification bool
|
disableCertificateFingerprintVerification bool
|
||||||
disableSRTPReplayProtection bool
|
disableSRTPReplayProtection bool
|
||||||
@@ -245,3 +247,65 @@ func (e *SettingEngine) DisableSRTCPReplayProtection(isDisabled bool) {
|
|||||||
func (e *SettingEngine) SetSDPMediaLevelFingerprints(sdpMediaLevelFingerprints bool) {
|
func (e *SettingEngine) SetSDPMediaLevelFingerprints(sdpMediaLevelFingerprints bool) {
|
||||||
e.sdpMediaLevelFingerprints = sdpMediaLevelFingerprints
|
e.sdpMediaLevelFingerprints = sdpMediaLevelFingerprints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddSDPExtensions adds available and offered extensions for media type.
|
||||||
|
//
|
||||||
|
// Ext IDs are optional and generated if you do not provide them
|
||||||
|
// SDP answers will only include extensions supported by both sides
|
||||||
|
func (e *SettingEngine) AddSDPExtensions(mediaType SDPSectionType, exts []sdp.ExtMap) {
|
||||||
|
if e.sdpExtensions == nil {
|
||||||
|
e.sdpExtensions = make(map[SDPSectionType][]sdp.ExtMap)
|
||||||
|
}
|
||||||
|
if _, ok := e.sdpExtensions[mediaType]; !ok {
|
||||||
|
e.sdpExtensions[mediaType] = []sdp.ExtMap{}
|
||||||
|
}
|
||||||
|
e.sdpExtensions[mediaType] = append(e.sdpExtensions[mediaType], exts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *SettingEngine) getSDPExtensions() map[SDPSectionType][]sdp.ExtMap {
|
||||||
|
var lastID int
|
||||||
|
idMap := map[string]int{}
|
||||||
|
|
||||||
|
// Build provided ext id map
|
||||||
|
for _, extList := range e.sdpExtensions {
|
||||||
|
for _, ext := range extList {
|
||||||
|
if ext.Value != 0 {
|
||||||
|
idMap[ext.URI.String()] = ext.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find next available ID
|
||||||
|
nextID := func() {
|
||||||
|
var done bool
|
||||||
|
for !done {
|
||||||
|
lastID++
|
||||||
|
var found bool
|
||||||
|
for _, v := range idMap {
|
||||||
|
if lastID == v {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign missing IDs across all media types based on URI
|
||||||
|
for mType, extList := range e.sdpExtensions {
|
||||||
|
for i, ext := range extList {
|
||||||
|
if ext.Value == 0 {
|
||||||
|
if id, ok := idMap[ext.URI.String()]; ok {
|
||||||
|
e.sdpExtensions[mType][i].Value = id
|
||||||
|
} else {
|
||||||
|
nextID()
|
||||||
|
e.sdpExtensions[mType][i].Value = lastID
|
||||||
|
idMap[ext.URI.String()] = lastID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e.sdpExtensions
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user