add Transport.Profile, media.Profile (#873)

this will allow to support AVPF in the future.
This commit is contained in:
Alessandro Ros
2025-09-04 18:08:51 +02:00
committed by GitHub
parent d2cb011812
commit cf2ff2b564
14 changed files with 142 additions and 85 deletions

View File

@@ -202,9 +202,9 @@ func prepareForAnnounce(
) error {
for i, m := range desc.Medias {
m.Control = "trackID=" + strconv.FormatInt(int64(i), 10)
m.Secure = secure
if secure {
m.Profile = headers.TransportProfileSAVP
announceDataMedia := announceData[m]
ssrcs := make([]uint32, len(m.Formats))
@@ -232,6 +232,8 @@ func prepareForAnnounce(
}
m.KeyMgmtMikey = mikeyMsg
} else {
m.Profile = headers.TransportProfileAVP
}
}
@@ -512,8 +514,8 @@ type Client struct {
lastDescribeDesc *description.Session
baseURL *base.URL
announceData map[*description.Media]*clientAnnounceDataMedia // record
effectiveTransport *Transport
effectiveSecure bool
setuppedTransport *Transport
setuppedProfile headers.TransportProfile
backChannelSetupped bool
stdChannelSetupped bool
setuppedMedias map[*description.Media]*clientMedia
@@ -973,7 +975,7 @@ func (c *Client) reset() {
c.optionsSent = false
c.useGetParameter = false
c.baseURL = nil
c.effectiveTransport = nil
c.setuppedTransport = nil
c.backChannelSetupped = false
c.stdChannelSetupped = false
c.setuppedMedias = nil
@@ -1004,7 +1006,7 @@ func (c *Client) trySwitchingProtocol() error {
c.reset()
v := TransportTCP
c.effectiveTransport = &v
c.setuppedTransport = &v
// some Hikvision cameras require a describe before a setup
_, _, err := c.doDescribe(c.lastDescribeURL)
@@ -1040,18 +1042,18 @@ func (c *Client) startTransportRoutines() {
cm.start()
}
if *c.effectiveTransport == TransportTCP {
if *c.setuppedTransport == TransportTCP {
c.tcpFrame = &base.InterleavedFrame{}
c.tcpBuffer = make([]byte, c.MaxPacketSize+4)
}
// always enable keepalives unless we are recording with TCP
if c.state == clientStatePlay || *c.effectiveTransport != TransportTCP {
if c.state == clientStatePlay || *c.setuppedTransport != TransportTCP {
c.keepAliveTimer = time.NewTimer(c.keepAlivePeriod)
}
if c.state == clientStatePlay && c.stdChannelSetupped {
switch *c.effectiveTransport {
switch *c.setuppedTransport {
case TransportUDP:
c.checkTimeoutTimer = time.NewTimer(c.InitialUDPReadTimeout)
c.checkTimeoutInitial = true
@@ -1066,7 +1068,7 @@ func (c *Client) startTransportRoutines() {
}
}
if *c.effectiveTransport == TransportTCP {
if *c.setuppedTransport == TransportTCP {
c.reader.setAllowInterleavedFrames(true)
}
}
@@ -1281,8 +1283,8 @@ func (c *Client) isInTCPTimeout() bool {
}
func (c *Client) doCheckTimeout() error {
if *c.effectiveTransport == TransportUDP ||
*c.effectiveTransport == TransportUDPMulticast {
if *c.setuppedTransport == TransportUDP ||
*c.setuppedTransport == TransportUDPMulticast {
if c.checkTimeoutInitial && !c.backChannelSetupped && c.Transport == nil {
c.checkTimeoutInitial = false
@@ -1586,20 +1588,30 @@ func (c *Client) doSetup(
switch {
// use transport from previous SETUP calls
case c.effectiveTransport != nil:
transport = *c.effectiveTransport
th.Secure = c.effectiveSecure
case c.setuppedTransport != nil:
transport = *c.setuppedTransport
th.Profile = c.setuppedProfile
// use transport from config, secure flag from server
case c.Transport != nil:
transport = *c.Transport
th.Secure = medi.Secure && c.Scheme == "rtsps"
if isSecure(medi.Profile) && c.Scheme == "rtsps" {
th.Profile = headers.TransportProfileSAVP
} else {
th.Profile = headers.TransportProfileAVP
}
// try UDP if unencrypted or secure is supported by server, otherwise try TCP
// try
// - UDP if unencrypted or secure is supported by server
// - otherwise, TCP
default:
th.Secure = medi.Secure && c.Scheme == "rtsps"
if isSecure(medi.Profile) && c.Scheme == "rtsps" {
th.Profile = headers.TransportProfileSAVP
} else {
th.Profile = headers.TransportProfileAVP
}
if th.Secure || c.Scheme == "rtsp" {
if th.Profile == headers.TransportProfileSAVP || c.Scheme == "rtsp" {
transport = TransportUDP
} else {
transport = TransportTCP
@@ -1609,7 +1621,7 @@ func (c *Client) doSetup(
cm := &clientMedia{
c: c,
media: medi,
secure: th.Secure,
secure: isSecure(th.Profile),
}
err = cm.initialize()
if err != nil {
@@ -1618,7 +1630,7 @@ func (c *Client) doSetup(
switch transport {
case TransportUDP, TransportUDPMulticast:
if c.Scheme == "rtsps" && !th.Secure {
if c.Scheme == "rtsps" && !isSecure(th.Profile) {
cm.close()
return nil, fmt.Errorf("unable to setup secure UDP")
}
@@ -1683,7 +1695,7 @@ func (c *Client) doSetup(
header["Require"] = base.HeaderValue{"www.onvif.org/ver20/backchannel"}
}
if th.Secure {
if isSecure(th.Profile) {
ssrcs := make([]uint32, len(cm.formats))
n := 0
for _, cf := range cm.formats {
@@ -1726,11 +1738,11 @@ func (c *Client) doSetup(
// switch transport automatically
if res.StatusCode == base.StatusUnsupportedTransport &&
c.effectiveTransport == nil && c.Transport == nil {
c.setuppedTransport == nil && c.Transport == nil {
c.OnTransportSwitch(liberrors.ErrClientSwitchToTCP2{})
v := TransportTCP
c.effectiveTransport = &v
c.effectiveSecure = th.Secure
c.setuppedTransport = &v
c.setuppedProfile = th.Profile
return c.doSetup(baseURL, medi, 0, 0)
}
@@ -1751,7 +1763,7 @@ func (c *Client) doSetup(
cm.close()
// switch transport automatically
if c.effectiveTransport == nil && c.Transport == nil {
if c.setuppedTransport == nil && c.Transport == nil {
c.OnTransportSwitch(liberrors.ErrClientSwitchToTCP2{})
c.baseURL = baseURL
@@ -1759,8 +1771,8 @@ func (c *Client) doSetup(
c.reset()
v := TransportTCP
c.effectiveTransport = &v
c.effectiveSecure = th.Secure
c.setuppedTransport = &v
c.setuppedProfile = th.Profile
// some Hikvision cameras require a describe before a setup
_, _, err = c.doDescribe(c.lastDescribeURL)
@@ -1906,12 +1918,12 @@ func (c *Client) doSetup(
cm.tcpChannel = thRes.InterleavedIDs[0]
}
if cm.secure {
if !thRes.Secure {
cm.close()
return nil, fmt.Errorf("transport was not setupped securely")
}
if thRes.Profile != th.Profile {
cm.close()
return nil, fmt.Errorf("returned profile does not match requested profile")
}
if cm.secure {
var mikeyMsg *mikey.Message
// extract key-mgmt from (in order of priority):
@@ -1943,9 +1955,6 @@ func (c *Client) doSetup(
cm.close()
return nil, err
}
} else if thRes.Secure {
cm.close()
return nil, fmt.Errorf("received unexpected secure profile")
}
if c.setuppedMedias == nil {
@@ -1955,8 +1964,8 @@ func (c *Client) doSetup(
c.setuppedMedias[medi] = cm
c.baseURL = baseURL
c.effectiveTransport = &transport
c.effectiveSecure = th.Secure
c.setuppedTransport = &transport
c.setuppedProfile = th.Profile
if medi.IsBackChannel {
c.backChannelSetupped = true
@@ -2057,7 +2066,7 @@ func (c *Client) doPlay(ra *headers.Range) (*base.Response, error) {
// when protocol is UDP,
// open the firewall by sending empty packets to the remote part.
// do this before sending the PLAY request.
if *c.effectiveTransport == TransportUDP {
if *c.setuppedTransport == TransportUDP {
for _, cm := range c.setuppedMedias {
if !cm.media.IsBackChannel && cm.udpRTPListener.writeAddr != nil {
buf, _ := (&rtp.Packet{Header: rtp.Header{Version: 2}}).Marshal()

View File

@@ -399,11 +399,11 @@ func TestClientPlay(t *testing.T) {
h := base.Header{}
th := headers.Transport{
Secure: inTH.Secure,
Profile: inTH.Profile,
}
if ca.secure == "secure" {
require.True(t, inTH.Secure)
require.Equal(t, headers.TransportProfileSAVP, inTH.Profile)
var keyMgmt headers.KeyMgmt
err2 = keyMgmt.Unmarshal(req.Header["KeyMgmt"])

View File

@@ -213,7 +213,7 @@ func TestClientRecord(t *testing.T) {
require.NoError(t, err2)
if ca.secure == "secure" {
require.True(t, desc2.Medias[0].Secure)
require.Equal(t, headers.TransportProfileSAVP, desc2.Medias[0].Profile)
_, err = mikeyToContext(desc2.Medias[0].KeyMgmtMikey)
require.NoError(t, err)
@@ -257,16 +257,14 @@ func TestClientRecord(t *testing.T) {
th := headers.Transport{
Delivery: deliveryPtr(headers.TransportDeliveryUnicast),
Secure: inTH.Secure,
Profile: inTH.Profile,
}
var srtpInCtx *wrappedSRTPContext
var srtpOutCtx *wrappedSRTPContext
if ca.secure == "secure" {
th.Secure = true
require.True(t, th.Secure)
require.Equal(t, inTH.Profile, headers.TransportProfileSAVP)
var keyMgmt headers.KeyMgmt
err = keyMgmt.Unmarshal(req.Header["KeyMgmt"])

View File

@@ -15,6 +15,7 @@ import (
"github.com/bluenviron/gortsplib/v4/pkg/base"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
)
@@ -78,15 +79,20 @@ type Media struct {
// Whether this media is a back channel.
IsBackChannel bool
// Control attribute.
Control string
// Whether the transport is secure.
//
// Deprecated: replaced by Profile
Secure bool
// RTP Profile.
Profile headers.TransportProfile
// key-mgmt attribute.
KeyMgmtMikey *mikey.Message
// Control attribute.
Control string
// Formats contained into the media.
Formats []format.Format
}
@@ -101,8 +107,14 @@ func (m *Media) Unmarshal(md *psdp.MediaDescription) error {
}
m.IsBackChannel = isBackChannel(md.Attributes)
m.Control = getAttribute(md.Attributes, "control")
m.Secure = slices.Contains(md.MediaName.Protos, "SAVP")
if slices.Contains(md.MediaName.Protos, "SAVP") {
m.Secure = true
m.Profile = headers.TransportProfileSAVP
} else {
m.Secure = false
m.Profile = headers.TransportProfileAVP
}
if enc := getAttribute(md.Attributes, "key-mgmt"); enc != "" {
if !strings.HasPrefix(enc, "mikey ") {
@@ -121,6 +133,8 @@ func (m *Media) Unmarshal(md *psdp.MediaDescription) error {
}
}
m.Control = getAttribute(md.Attributes, "control")
m.Formats = nil
for _, payloadType := range md.MediaName.Formats {
@@ -152,11 +166,16 @@ func (m Media) Marshal() *psdp.MediaDescription {
// Marshal2 encodes the media in SDP format.
func (m Media) Marshal2() (*psdp.MediaDescription, error) {
if m.Secure {
m.Profile = headers.TransportProfileSAVP
}
var protos []string
if !m.Secure {
protos = []string{"RTP", "AVP"}
} else {
if m.Profile == headers.TransportProfileSAVP {
protos = []string{"RTP", "SAVP"}
} else {
protos = []string{"RTP", "AVP"}
}
md := &psdp.MediaDescription{

View File

@@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/bluenviron/gortsplib/v4/pkg/format"
"github.com/bluenviron/gortsplib/v4/pkg/headers"
"github.com/bluenviron/gortsplib/v4/pkg/mikey"
"github.com/bluenviron/gortsplib/v4/pkg/sdp"
)
@@ -726,6 +727,7 @@ var casesSession = []struct {
Type: "video",
Control: "trackID=0",
Secure: true,
Profile: headers.TransportProfileSAVP,
Formats: []format.Format{&format.H264{
PayloadTyp: 96,
}},
@@ -762,6 +764,7 @@ var casesSession = []struct {
Type: "video",
Control: "trackID=0",
Secure: true,
Profile: headers.TransportProfileSAVP,
KeyMgmtMikey: &mikey.Message{ //nolint:dupl
Header: mikey.Header{
Version: 1,

View File

@@ -38,6 +38,15 @@ func parsePorts(val string) (*[2]int, error) {
return &[2]int{0, 0}, fmt.Errorf("invalid ports (%v)", val)
}
// TransportProfile is a transport profile.
type TransportProfile int
// transport profiles.
const (
TransportProfileAVP TransportProfile = iota
TransportProfileSAVP
)
// TransportProtocol is a transport protocol.
type TransportProtocol int
@@ -116,13 +125,18 @@ func (m TransportMode) String() string {
// Transport is a Transport header.
type Transport struct {
// protocol of the stream.
Protocol TransportProtocol
// Whether the secure variant is active.
//
// Deprecated: replaced by Profile.
Secure bool
// (optional) delivery method of the stream.
// profile.
Profile TransportProfile
// protocol.
Protocol TransportProtocol
// (optional) delivery method.
Delivery *TransportDelivery
// (optional) Source IP.
@@ -146,7 +160,7 @@ type Transport struct {
// (optional) server ports.
ServerPorts *[2]int
// (optional) SSRC of the packets of the stream.
// (optional) SSRC of packets.
SSRC *uint32
// (optional) mode.
@@ -175,21 +189,25 @@ func (h *Transport) Unmarshal(v base.HeaderValue) error {
switch k {
case "RTP/AVP", "RTP/AVP/UDP":
h.Profile = TransportProfileAVP
h.Protocol = TransportProtocolUDP
profileFound = true
case "RTP/AVP/TCP":
h.Profile = TransportProfileAVP
h.Protocol = TransportProtocolTCP
profileFound = true
case "RTP/SAVP", "RTP/SAVP/UDP":
h.Protocol = TransportProtocolUDP
h.Secure = true
h.Profile = TransportProfileSAVP
profileFound = true
case "RTP/SAVP/TCP":
h.Protocol = TransportProtocolTCP
h.Secure = true
h.Profile = TransportProfileSAVP
h.Protocol = TransportProtocolTCP
profileFound = true
case "unicast":
@@ -299,16 +317,20 @@ func (h *Transport) Unmarshal(v base.HeaderValue) error {
func (h Transport) Marshal() base.HeaderValue {
var rets []string
if h.Secure {
h.Profile = TransportProfileSAVP
}
var profile string
switch {
case h.Protocol == TransportProtocolUDP && !h.Secure:
case h.Protocol == TransportProtocolUDP && h.Profile == TransportProfileAVP:
profile = "RTP/AVP"
case h.Protocol == TransportProtocolTCP && !h.Secure:
case h.Protocol == TransportProtocolTCP && h.Profile == TransportProfileAVP:
profile = "RTP/AVP/TCP"
case h.Protocol == TransportProtocolUDP && h.Secure:
case h.Protocol == TransportProtocolUDP && h.Profile == TransportProfileSAVP:
profile = "RTP/SAVP"
case h.Protocol == TransportProtocolTCP && h.Secure:
case h.Protocol == TransportProtocolTCP && h.Profile == TransportProfileSAVP:
profile = "RTP/SAVP/TCP"
}

View File

@@ -175,6 +175,7 @@ var casesTransport = []struct {
Transport{
Protocol: TransportProtocolUDP,
Secure: true,
Profile: TransportProfileSAVP,
Delivery: deliveryPtr(TransportDeliveryUnicast),
ClientPorts: &[2]int{3456, 3457},
Mode: transportModePtr(TransportModePlay),
@@ -187,6 +188,7 @@ var casesTransport = []struct {
Transport{
Protocol: TransportProtocolTCP,
Secure: true,
Profile: TransportProfileSAVP,
InterleavedIDs: &[2]int{0, 1},
},
},

View File

@@ -777,7 +777,7 @@ func TestServerPlay(t *testing.T) {
desc := doDescribe(t, conn, false)
if ca.secure == "secure" {
require.True(t, desc.Medias[0].Secure)
require.Equal(t, headers.TransportProfileSAVP, desc.Medias[0].Profile)
require.NotEmpty(t, desc.Medias[0].KeyMgmtMikey)
}
@@ -811,7 +811,7 @@ func TestServerPlay(t *testing.T) {
var srtpOutCtx *wrappedSRTPContext
if ca.secure == "secure" {
inTH.Secure = true
inTH.Profile = headers.TransportProfileSAVP
key := make([]byte, srtpKeyLength)
_, err = rand.Read(key)
@@ -854,7 +854,7 @@ func TestServerPlay(t *testing.T) {
var srtpInCtx *wrappedSRTPContext
if ca.secure == "secure" {
require.True(t, th.Secure)
require.Equal(t, headers.TransportProfileSAVP, th.Profile)
var keyMgmt headers.KeyMgmt
err = keyMgmt.Unmarshal(res.Header["KeyMgmt"])

View File

@@ -752,7 +752,7 @@ func TestServerRecord(t *testing.T) {
}
if ca.secure == "secure" {
inTH.Secure = true
inTH.Profile = headers.TransportProfileSAVP
key := make([]byte, srtpKeyLength)
_, err = rand.Read(key)
@@ -800,7 +800,7 @@ func TestServerRecord(t *testing.T) {
}
if ca.secure == "secure" {
require.True(t, th.Secure)
require.Equal(t, headers.TransportProfileSAVP, th.Profile)
var keyMgmt headers.KeyMgmt
err = keyMgmt.Unmarshal(res.Header["KeyMgmt"])

View File

@@ -31,6 +31,10 @@ import (
type readFunc func([]byte) bool
func isSecure(profile headers.TransportProfile) bool {
return profile == headers.TransportProfileSAVP
}
func stringsReverseIndex(s, substr string) int {
for i := len(s) - 1 - len(substr); i >= 0; i-- {
if s[i:i+len(substr)] == substr {
@@ -181,12 +185,12 @@ func isTransportSupported(s *Server, tr *headers.Transport) bool {
}
// prevent using unsecure UDP with RTSPS
if tr.Protocol == headers.TransportProtocolUDP && !tr.Secure && s.TLSConfig != nil {
if tr.Protocol == headers.TransportProtocolUDP && !isSecure(tr.Profile) && s.TLSConfig != nil {
return false
}
// prevent using secure profiles with plain RTSP, since keys are in plain
if tr.Secure && s.TLSConfig == nil {
if isSecure(tr.Profile) && s.TLSConfig == nil {
return false
}
@@ -420,7 +424,7 @@ type ServerSession struct {
setuppedMediasOrdered []*serverSessionMedia
tcpCallbackByChannel map[int]readFunc
setuppedTransport *Transport
setuppedSecure bool
setuppedProfile headers.TransportProfile
setuppedStream *ServerStream // play
setuppedPath string
setuppedQuery string
@@ -503,7 +507,7 @@ func (ss *ServerSession) SetuppedTransport() *Transport {
// If this is false, it does not mean that the stream is not secure, since
// there are some combinations that are secure nonetheless, like RTSPS+TCP+unsecure.
func (ss *ServerSession) SetuppedSecure() bool {
return ss.setuppedSecure
return isSecure(ss.setuppedProfile)
}
// SetuppedStream returns the stream associated with the session.
@@ -1126,7 +1130,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
var srtpInCtx *wrappedSRTPContext
if inTH.Secure {
if isSecure(inTH.Profile) {
var keyMgmt headers.KeyMgmt
err = keyMgmt.Unmarshal(req.Header["KeyMgmt"])
if err != nil {
@@ -1143,7 +1147,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
}
}
if ss.setuppedTransport != nil && (*ss.setuppedTransport != transport || ss.setuppedSecure != inTH.Secure) {
if ss.setuppedTransport != nil && (*ss.setuppedTransport != transport || ss.setuppedProfile != inTH.Profile) {
return &base.Response{
StatusCode: base.StatusBadRequest,
}, liberrors.ErrServerMediasDifferentTransports{}
@@ -1247,7 +1251,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
}
ss.setuppedTransport = &transport
ss.setuppedSecure = inTH.Secure
ss.setuppedProfile = inTH.Profile
if ss.state == ServerSessionStateInitial {
err = stream.readerAdd(ss,
@@ -1266,7 +1270,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
}
th := headers.Transport{
Secure: inTH.Secure,
Profile: inTH.Profile,
}
if ss.state == ServerSessionStatePrePlay {
@@ -1355,7 +1359,7 @@ func (ss *ServerSession) handleRequestInner(sc *ServerConn, req *base.Request) (
res.Header["Transport"] = th.Marshal()
if inTH.Secure {
if isSecure(inTH.Profile) {
ssrcs := make([]uint32, len(sm.formats))
n := 0
for _, sf := range sm.formats {

View File

@@ -159,7 +159,7 @@ func (sf *serverSessionFormat) writePacketRTP(pkt *rtp.Packet) error {
pkt.SSRC = sf.localSSRC
maxPlainPacketSize := sf.sm.ss.s.MaxPacketSize
if sf.sm.ss.setuppedSecure {
if isSecure(sf.sm.ss.setuppedProfile) {
maxPlainPacketSize -= srtpOverhead
}
@@ -171,7 +171,7 @@ func (sf *serverSessionFormat) writePacketRTP(pkt *rtp.Packet) error {
plain = plain[:n]
var encr []byte
if sf.sm.ss.setuppedSecure {
if isSecure(sf.sm.ss.setuppedProfile) {
encr = make([]byte, sf.sm.ss.s.MaxPacketSize)
encr, err = sf.sm.srtpOutCtx.encryptRTP(encr, plain, &pkt.Header)
if err != nil {
@@ -179,7 +179,7 @@ func (sf *serverSessionFormat) writePacketRTP(pkt *rtp.Packet) error {
}
}
if sf.sm.ss.setuppedSecure {
if isSecure(sf.sm.ss.setuppedProfile) {
return sf.writePacketRTPEncoded(encr)
}
return sf.writePacketRTPEncoded(plain)

View File

@@ -459,7 +459,7 @@ func (sm *serverSessionMedia) writePacketRTCP(pkt rtcp.Packet) error {
}
maxPlainPacketSize := sm.ss.s.MaxPacketSize
if sm.ss.setuppedSecure {
if isSecure(sm.ss.setuppedProfile) {
maxPlainPacketSize -= srtcpOverhead
}
@@ -468,7 +468,7 @@ func (sm *serverSessionMedia) writePacketRTCP(pkt rtcp.Packet) error {
}
var encr []byte
if sm.ss.setuppedSecure {
if isSecure(sm.ss.setuppedProfile) {
encr = make([]byte, sm.ss.s.MaxPacketSize)
encr, err = sm.srtpOutCtx.encryptRTCP(encr, plain, nil)
if err != nil {
@@ -476,7 +476,7 @@ func (sm *serverSessionMedia) writePacketRTCP(pkt rtcp.Packet) error {
}
}
if sm.ss.setuppedSecure {
if isSecure(sm.ss.setuppedProfile) {
return sm.writePacketRTCPEncoded(encr)
}
return sm.writePacketRTCPEncoded(plain)

View File

@@ -120,7 +120,7 @@ func (sf *serverStreamFormat) writePacketRTP(pkt *rtp.Packet, ntp time.Time) err
if rsm, ok := r.setuppedMedias[sf.sm.media]; ok {
rsf := rsm.formats[pkt.PayloadType]
if r.setuppedSecure {
if isSecure(r.setuppedProfile) {
err = rsf.writePacketRTPEncoded(encr)
if err != nil {
r.onStreamWriteError(err)

View File

@@ -110,7 +110,7 @@ func (sm *serverStreamMedia) writePacketRTCP(pkt rtcp.Packet) error {
// send unicast
for r := range sm.st.activeUnicastReaders {
if sm, ok := r.setuppedMedias[sm.media]; ok {
if r.setuppedSecure {
if isSecure(r.setuppedProfile) {
err = sm.writePacketRTCPEncoded(encr)
if err != nil {
r.onStreamWriteError(err)