mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 15:16:51 +08:00
client: build track URLs by using Content-Base, when provided by server
This commit is contained in:
@@ -71,24 +71,24 @@ func (s clientConnState) String() string {
|
||||
|
||||
// ClientConn is a client-side RTSP connection.
|
||||
type ClientConn struct {
|
||||
conf ClientConf
|
||||
nconn net.Conn
|
||||
isTLS bool
|
||||
br *bufio.Reader
|
||||
bw *bufio.Writer
|
||||
session string
|
||||
cseq int
|
||||
sender *auth.Sender
|
||||
state clientConnState
|
||||
streamURL *base.URL
|
||||
streamProtocol *StreamProtocol
|
||||
tracks map[int]clientConnTrack
|
||||
getParameterSupported bool
|
||||
writeMutex sync.Mutex
|
||||
writeFrameAllowed bool
|
||||
writeError error
|
||||
backgroundRunning bool
|
||||
readCB func(int, StreamType, []byte)
|
||||
conf ClientConf
|
||||
nconn net.Conn
|
||||
isTLS bool
|
||||
br *bufio.Reader
|
||||
bw *bufio.Writer
|
||||
session string
|
||||
cseq int
|
||||
sender *auth.Sender
|
||||
state clientConnState
|
||||
streamBaseURL *base.URL
|
||||
streamProtocol *StreamProtocol
|
||||
tracks map[int]clientConnTrack
|
||||
useGetParameter bool
|
||||
writeMutex sync.Mutex
|
||||
writeFrameAllowed bool
|
||||
writeError error
|
||||
backgroundRunning bool
|
||||
readCB func(int, StreamType, []byte)
|
||||
|
||||
// TCP stream protocol
|
||||
tcpFrameBuffer *multibuffer.MultiBuffer
|
||||
@@ -161,7 +161,7 @@ func (cc *ClientConn) Close() error {
|
||||
if cc.state == clientConnStatePlay || cc.state == clientConnStateRecord {
|
||||
cc.Do(&base.Request{
|
||||
Method: base.Teardown,
|
||||
URL: cc.streamURL,
|
||||
URL: cc.streamBaseURL,
|
||||
SkipResponse: true,
|
||||
})
|
||||
}
|
||||
@@ -185,10 +185,10 @@ func (cc *ClientConn) reset() {
|
||||
|
||||
cc.state = clientConnStateInitial
|
||||
cc.nconn = nil
|
||||
cc.streamURL = nil
|
||||
cc.streamBaseURL = nil
|
||||
cc.streamProtocol = nil
|
||||
cc.tracks = make(map[int]clientConnTrack)
|
||||
cc.getParameterSupported = false
|
||||
cc.useGetParameter = false
|
||||
cc.backgroundRunning = false
|
||||
|
||||
// read
|
||||
@@ -375,7 +375,7 @@ func (cc *ClientConn) Options(u *base.URL) (*base.Response, error) {
|
||||
return res, liberrors.ErrClientWrongStatusCode{Code: res.StatusCode, Message: res.StatusMessage}
|
||||
}
|
||||
|
||||
cc.getParameterSupported = func() bool {
|
||||
cc.useGetParameter = func() bool {
|
||||
pub, ok := res.Header["Public"]
|
||||
if !ok || len(pub) != 1 {
|
||||
return false
|
||||
@@ -453,7 +453,29 @@ func (cc *ClientConn) Describe(u *base.URL) (Tracks, *base.Response, error) {
|
||||
return nil, nil, liberrors.ErrClientContentTypeUnsupported{CT: ct}
|
||||
}
|
||||
|
||||
tracks, err := ReadTracks(res.Body, u)
|
||||
baseURL, err := func() (*base.URL, error) {
|
||||
// prefer Content-Base (optional)
|
||||
if cb, ok := res.Header["Content-Base"]; ok {
|
||||
if len(cb) != 1 {
|
||||
return nil, fmt.Errorf("invalid Content-Base: '%v'", cb)
|
||||
}
|
||||
|
||||
ret, err := base.ParseURL(cb[0])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid Content-Base: '%v'", cb)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// if not provided, use DESCRIBE URL
|
||||
return u, nil
|
||||
}()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tracks, err := ReadTracks(res.Body, baseURL)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
@@ -481,7 +503,7 @@ func (cc *ClientConn) Setup(mode headers.TransportMode, track *Track,
|
||||
return nil, liberrors.ErrClientCannotReadPublishAtSameTime{}
|
||||
}
|
||||
|
||||
if cc.streamURL != nil && *track.BaseURL != *cc.streamURL {
|
||||
if cc.streamBaseURL != nil && *track.BaseURL != *cc.streamBaseURL {
|
||||
return nil, liberrors.ErrClientCannotSetupTracksDifferentURLs{}
|
||||
}
|
||||
|
||||
@@ -670,7 +692,7 @@ func (cc *ClientConn) Setup(mode headers.TransportMode, track *Track,
|
||||
cct.rtcpSender = rtcpsender.New(clockRate)
|
||||
}
|
||||
|
||||
cc.streamURL = track.BaseURL
|
||||
cc.streamBaseURL = track.BaseURL
|
||||
cc.streamProtocol = &proto
|
||||
|
||||
if proto == StreamProtocolUDP {
|
||||
@@ -726,7 +748,7 @@ func (cc *ClientConn) Pause() (*base.Response, error) {
|
||||
|
||||
res, err := cc.Do(&base.Request{
|
||||
Method: base.Pause,
|
||||
URL: cc.streamURL,
|
||||
URL: cc.streamBaseURL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -837,23 +859,19 @@ func (cc *ClientConn) ReadFrames(onFrame func(int, StreamType, []byte)) chan err
|
||||
safeState == clientConnStatePlay {
|
||||
if _, ok := err.(liberrors.ErrClientNoUDPPacketsRecently); ok {
|
||||
if cc.conf.StreamProtocol == nil {
|
||||
prevURL := cc.streamURL
|
||||
prevBaseURL := cc.streamBaseURL
|
||||
oldUseGetParameter := cc.useGetParameter
|
||||
prevTracks := cc.tracks
|
||||
cc.reset()
|
||||
v := StreamProtocolTCP
|
||||
cc.streamProtocol = &v
|
||||
cc.useGetParameter = oldUseGetParameter
|
||||
|
||||
err := cc.connOpen(prevURL.Scheme, prevURL.Host)
|
||||
err := cc.connOpen(prevBaseURL.Scheme, prevBaseURL.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = cc.Options(prevURL)
|
||||
if err != nil {
|
||||
cc.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
for _, track := range prevTracks {
|
||||
_, err := cc.Setup(headers.TransportModePlay, track.track, 0, 0)
|
||||
if err != nil {
|
||||
|
@@ -20,11 +20,14 @@ func (cc *ClientConn) Announce(u *base.URL, tracks Tracks) (*base.Response, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// in case of ANNOUNCE, the base URL doesn't have a trailing slash.
|
||||
// (tested with ffmpeg and gstreamer)
|
||||
baseURL := u.Clone()
|
||||
|
||||
// set id, base url and control attribute on tracks
|
||||
for i, t := range tracks {
|
||||
t.ID = i
|
||||
t.BaseURL = u
|
||||
|
||||
t.BaseURL = baseURL
|
||||
t.Media.Attributes = append(t.Media.Attributes, psdp.Attribute{
|
||||
Key: "control",
|
||||
Value: "trackID=" + strconv.FormatInt(int64(i), 10),
|
||||
@@ -48,7 +51,7 @@ func (cc *ClientConn) Announce(u *base.URL, tracks Tracks) (*base.Response, erro
|
||||
Code: res.StatusCode, Message: res.StatusMessage}
|
||||
}
|
||||
|
||||
cc.streamURL = u
|
||||
cc.streamBaseURL = baseURL
|
||||
cc.state = clientConnStatePreRecord
|
||||
|
||||
return res, nil
|
||||
@@ -66,7 +69,7 @@ func (cc *ClientConn) Record() (*base.Response, error) {
|
||||
|
||||
res, err := cc.Do(&base.Request{
|
||||
Method: base.Record,
|
||||
URL: cc.streamURL,
|
||||
URL: cc.streamBaseURL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -40,6 +40,7 @@ func TestClientPublishSerial(t *testing.T) {
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Options, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
@@ -56,6 +57,7 @@ func TestClientPublishSerial(t *testing.T) {
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Announce, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
@@ -65,6 +67,7 @@ func TestClientPublishSerial(t *testing.T) {
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Setup, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream/trackID=0"), req.URL)
|
||||
|
||||
var inTH headers.Transport
|
||||
err = inTH.Read(req.Header["Transport"])
|
||||
@@ -110,6 +113,7 @@ func TestClientPublishSerial(t *testing.T) {
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Record, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
@@ -151,6 +155,7 @@ func TestClientPublishSerial(t *testing.T) {
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Teardown, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
|
@@ -22,7 +22,7 @@ func (cc *ClientConn) Play() (*base.Response, error) {
|
||||
|
||||
res, err := cc.Do(&base.Request{
|
||||
Method: base.Play,
|
||||
URL: cc.streamURL,
|
||||
URL: cc.streamBaseURL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -119,13 +119,13 @@ func (cc *ClientConn) backgroundPlayUDP() error {
|
||||
_, err := cc.Do(&base.Request{
|
||||
Method: func() base.Method {
|
||||
// the vlc integrated rtsp server requires GET_PARAMETER
|
||||
if cc.getParameterSupported {
|
||||
if cc.useGetParameter {
|
||||
return base.GetParameter
|
||||
}
|
||||
return base.Options
|
||||
}(),
|
||||
// use the stream path, otherwise some cameras do not reply
|
||||
URL: cc.streamURL,
|
||||
// use the stream base URL, otherwise some cameras do not reply
|
||||
URL: cc.streamBaseURL,
|
||||
SkipResponse: true,
|
||||
})
|
||||
if err != nil {
|
||||
|
@@ -74,6 +74,7 @@ func TestClientRead(t *testing.T) {
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Options, req.Method)
|
||||
require.Equal(t, base.MustParseURL(scheme+"://localhost:8554/teststream"), req.URL)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
@@ -90,6 +91,7 @@ func TestClientRead(t *testing.T) {
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Describe, req.Method)
|
||||
require.Equal(t, base.MustParseURL(scheme+"://localhost:8554/teststream"), req.URL)
|
||||
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
@@ -98,6 +100,7 @@ func TestClientRead(t *testing.T) {
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{scheme + "://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
@@ -106,6 +109,7 @@ func TestClientRead(t *testing.T) {
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Setup, req.Method)
|
||||
require.Equal(t, base.MustParseURL(scheme+"://localhost:8554/teststream/trackID=0"), req.URL)
|
||||
|
||||
var inTH headers.Transport
|
||||
err = inTH.Read(req.Header["Transport"])
|
||||
@@ -150,6 +154,7 @@ func TestClientRead(t *testing.T) {
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Play, req.Method)
|
||||
require.Equal(t, base.MustParseURL(scheme+"://localhost:8554/teststream/"), req.URL)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
@@ -194,6 +199,16 @@ func TestClientRead(t *testing.T) {
|
||||
}
|
||||
|
||||
close(frameRecv)
|
||||
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Teardown, req.Method)
|
||||
require.Equal(t, base.MustParseURL(scheme+"://localhost:8554/teststream/"), req.URL)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
conf := ClientConf{
|
||||
@@ -226,6 +241,108 @@ func TestClientRead(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientReadNoContentBase(t *testing.T) {
|
||||
l, err := net.Listen("tcp", "localhost:8554")
|
||||
require.NoError(t, err)
|
||||
defer l.Close()
|
||||
|
||||
serverDone := make(chan struct{})
|
||||
defer func() { <-serverDone }()
|
||||
go func() {
|
||||
defer close(serverDone)
|
||||
|
||||
conn, err := l.Accept()
|
||||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
bconn := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
||||
|
||||
var req base.Request
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Options, req.Method)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Public": base.HeaderValue{strings.Join([]string{
|
||||
string(base.Describe),
|
||||
string(base.Setup),
|
||||
string(base.Play),
|
||||
}, ", ")},
|
||||
},
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Describe, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||
|
||||
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Setup, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream/trackID=0"), req.URL)
|
||||
|
||||
var inTH headers.Transport
|
||||
err = inTH.Read(req.Header["Transport"])
|
||||
require.NoError(t, err)
|
||||
|
||||
th := headers.Transport{
|
||||
Delivery: func() *base.StreamDelivery {
|
||||
v := base.StreamDeliveryUnicast
|
||||
return &v
|
||||
}(),
|
||||
Protocol: StreamProtocolUDP,
|
||||
ClientPorts: inTH.ClientPorts,
|
||||
ServerPorts: &[2]int{34556, 34557},
|
||||
}
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Transport": th.Write(),
|
||||
},
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Play, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Teardown, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
conn, err := DialRead("rtsp://localhost:8554/teststream")
|
||||
require.NoError(t, err)
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
func TestClientReadAnyPort(t *testing.T) {
|
||||
for _, ca := range []string{
|
||||
"zero",
|
||||
@@ -274,6 +391,7 @@ func TestClientReadAnyPort(t *testing.T) {
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
@@ -392,6 +510,7 @@ func TestClientReadAutomaticProtocol(t *testing.T) {
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
@@ -497,6 +616,7 @@ func TestClientReadAutomaticProtocol(t *testing.T) {
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
@@ -505,6 +625,7 @@ func TestClientReadAutomaticProtocol(t *testing.T) {
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Setup, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream/trackID=0"), req.URL)
|
||||
|
||||
var inTH headers.Transport
|
||||
err = inTH.Read(req.Header["Transport"])
|
||||
@@ -552,25 +673,10 @@ func TestClientReadAutomaticProtocol(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
bconn = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
||||
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Options, req.Method)
|
||||
|
||||
err = base.Response{
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Public": base.HeaderValue{strings.Join([]string{
|
||||
string(base.Describe),
|
||||
string(base.Setup),
|
||||
string(base.Play),
|
||||
}, ", ")},
|
||||
},
|
||||
}.Write(bconn.Writer)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = req.Read(bconn.Reader)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, base.Setup, req.Method)
|
||||
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream/trackID=0"), req.URL)
|
||||
|
||||
inTH = headers.Transport{}
|
||||
err = inTH.Read(req.Header["Transport"])
|
||||
@@ -715,6 +821,7 @@ func TestClientReadRedirect(t *testing.T) {
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
@@ -869,6 +976,7 @@ func TestClientReadPause(t *testing.T) {
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
@@ -1042,6 +1150,7 @@ func TestClientReadRTCPReport(t *testing.T) {
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
@@ -1214,6 +1323,7 @@ func TestClientReadErrorTimeout(t *testing.T) {
|
||||
StatusCode: base.StatusOK,
|
||||
Header: base.Header{
|
||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||
},
|
||||
Body: Tracks{track}.Write(),
|
||||
}.Write(bconn.Writer)
|
||||
|
@@ -113,16 +113,3 @@ func (u *URL) RTSPPathAndQuery() (string, bool) {
|
||||
|
||||
return pathAndQuery, true
|
||||
}
|
||||
|
||||
// AddControlAttribute adds a control attribute to a RTSP url.
|
||||
func (u *URL) AddControlAttribute(controlPath string) {
|
||||
if controlPath[0] != '?' {
|
||||
controlPath = "/" + controlPath
|
||||
}
|
||||
|
||||
// insert the control attribute at the end of the url
|
||||
// if there's a query, insert it after the query
|
||||
// otherwise insert it after the path
|
||||
nu, _ := ParseURL(u.String() + controlPath)
|
||||
*u = *nu
|
||||
}
|
||||
|
@@ -94,50 +94,3 @@ func TestURLRTSPPathAndQuery(t *testing.T) {
|
||||
require.Equal(t, ca.b, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLAddControlAttribute(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
control string
|
||||
u *URL
|
||||
ou *URL
|
||||
}{
|
||||
{
|
||||
"trackID=1",
|
||||
MustParseURL("rtsp://localhost:8554/teststream"),
|
||||
MustParseURL("rtsp://localhost:8554/teststream/trackID=1"),
|
||||
},
|
||||
{
|
||||
"trackID=1",
|
||||
MustParseURL("rtsp://localhost:8554/test/stream"),
|
||||
MustParseURL("rtsp://localhost:8554/test/stream/trackID=1"),
|
||||
},
|
||||
{
|
||||
"trackID=1",
|
||||
MustParseURL("rtsp://192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp"),
|
||||
MustParseURL("rtsp://192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp/trackID=1"),
|
||||
},
|
||||
{
|
||||
"trackID=1",
|
||||
MustParseURL("rtsp://192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
|
||||
MustParseURL("rtsp://192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=1"),
|
||||
},
|
||||
{
|
||||
"trackID=1",
|
||||
MustParseURL("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
|
||||
MustParseURL("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=1"),
|
||||
},
|
||||
{
|
||||
"?ctype=video",
|
||||
MustParseURL("rtsp://192.168.1.99:554/"),
|
||||
MustParseURL("rtsp://192.168.1.99:554/?ctype=video"),
|
||||
},
|
||||
{
|
||||
"?ctype=video",
|
||||
MustParseURL("rtsp://192.168.1.99:554/test"),
|
||||
MustParseURL("rtsp://192.168.1.99:554/test?ctype=video"),
|
||||
},
|
||||
} {
|
||||
ca.u.AddControlAttribute(ca.control)
|
||||
require.Equal(t, ca.ou, ca.u)
|
||||
}
|
||||
}
|
||||
|
10
track.go
10
track.go
@@ -306,8 +306,14 @@ func (t *Track) URL() (*base.URL, error) {
|
||||
}
|
||||
|
||||
// control attribute contains a relative control attribute
|
||||
ur := t.BaseURL.Clone()
|
||||
ur.AddControlAttribute(controlAttr)
|
||||
// insert the control attribute at the end of the url
|
||||
// if there's a query, insert it after the query
|
||||
// otherwise insert it after the path
|
||||
strURL := t.BaseURL.String()
|
||||
if controlAttr[0] != '?' && !strings.HasSuffix(strURL, "/") {
|
||||
strURL += "/"
|
||||
}
|
||||
ur, _ := base.ParseURL(strURL + controlAttr)
|
||||
return ur, nil
|
||||
}
|
||||
|
||||
|
110
track_test.go
110
track_test.go
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
psdp "github.com/pion/sdp/v3"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aler9/gortsplib/pkg/base"
|
||||
)
|
||||
|
||||
func TestTrackClockRate(t *testing.T) {
|
||||
@@ -84,6 +86,114 @@ func TestTrackClockRate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrackURL(t *testing.T) {
|
||||
for _, ca := range []struct {
|
||||
name string
|
||||
sdp []byte
|
||||
baseURL *base.URL
|
||||
ur *base.URL
|
||||
}{
|
||||
{
|
||||
"missing control",
|
||||
[]byte("v=0\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000\r\n"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/"),
|
||||
},
|
||||
{
|
||||
"absolute control",
|
||||
[]byte("v=0\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000\r\n" +
|
||||
"a=control:rtsp://localhost/path/trackID=7"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/trackID=7"),
|
||||
},
|
||||
{
|
||||
"relative control",
|
||||
[]byte("v=0\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000\r\n" +
|
||||
"a=control:trackID=5"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/path/trackID=5"),
|
||||
},
|
||||
{
|
||||
"relative control, subpath",
|
||||
[]byte("v=0\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000\r\n" +
|
||||
"a=control:trackID=5"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/sub/path/"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/sub/path/trackID=5"),
|
||||
},
|
||||
{
|
||||
"relative control, url without slash",
|
||||
[]byte("v=0\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000\r\n" +
|
||||
"a=control:trackID=5"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/sub/path"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/sub/path/trackID=5"),
|
||||
},
|
||||
{
|
||||
"relative control, url with query",
|
||||
[]byte("v=0\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000\r\n" +
|
||||
"a=control:trackID=5"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp/trackID=5"),
|
||||
},
|
||||
{
|
||||
"relative control, url with special chars and query",
|
||||
[]byte("v=0\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000\r\n" +
|
||||
"a=control:trackID=5"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=5"),
|
||||
},
|
||||
{
|
||||
"relative control, url with query without question mark",
|
||||
[]byte("v=0\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000\r\n" +
|
||||
"a=control:trackID=5"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
|
||||
base.MustParseURL("rtsp://myuser:mypass@192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=5"),
|
||||
},
|
||||
{
|
||||
"relative control, control is query",
|
||||
[]byte("v=0\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000\r\n" +
|
||||
"a=control:?ctype=video"),
|
||||
base.MustParseURL("rtsp://192.168.1.99:554/test"),
|
||||
base.MustParseURL("rtsp://192.168.1.99:554/test?ctype=video"),
|
||||
},
|
||||
{
|
||||
"relative control, control is query and no path",
|
||||
[]byte("v=0\r\n" +
|
||||
"m=video 0 RTP/AVP 96\r\n" +
|
||||
"a=rtpmap:96 H264/90000\r\n" +
|
||||
"a=control:?ctype=video"),
|
||||
base.MustParseURL("rtsp://192.168.1.99:554/"),
|
||||
base.MustParseURL("rtsp://192.168.1.99:554/?ctype=video"),
|
||||
},
|
||||
} {
|
||||
t.Run(ca.name, func(t *testing.T) {
|
||||
tracks, err := ReadTracks(ca.sdp, nil)
|
||||
require.NoError(t, err)
|
||||
tracks[0].BaseURL = ca.baseURL
|
||||
ur, err := tracks[0].URL()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ca.ur, ur)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var testH264SPS = []byte("\x67\x64\x00\x0c\xac\x3b\x50\xb0\x4b\x42\x00\x00\x03\x00\x02\x00\x00\x03\x00\x3d\x08")
|
||||
|
||||
var testH264PPS = []byte("\x68\xee\x3c\x80")
|
||||
|
Reference in New Issue
Block a user