mirror of
https://github.com/aler9/gortsplib
synced 2025-10-05 23:26:54 +08:00
client: build track URLs by using Content-Base, when provided by server
This commit is contained in:
@@ -80,10 +80,10 @@ type ClientConn struct {
|
|||||||
cseq int
|
cseq int
|
||||||
sender *auth.Sender
|
sender *auth.Sender
|
||||||
state clientConnState
|
state clientConnState
|
||||||
streamURL *base.URL
|
streamBaseURL *base.URL
|
||||||
streamProtocol *StreamProtocol
|
streamProtocol *StreamProtocol
|
||||||
tracks map[int]clientConnTrack
|
tracks map[int]clientConnTrack
|
||||||
getParameterSupported bool
|
useGetParameter bool
|
||||||
writeMutex sync.Mutex
|
writeMutex sync.Mutex
|
||||||
writeFrameAllowed bool
|
writeFrameAllowed bool
|
||||||
writeError error
|
writeError error
|
||||||
@@ -161,7 +161,7 @@ func (cc *ClientConn) Close() error {
|
|||||||
if cc.state == clientConnStatePlay || cc.state == clientConnStateRecord {
|
if cc.state == clientConnStatePlay || cc.state == clientConnStateRecord {
|
||||||
cc.Do(&base.Request{
|
cc.Do(&base.Request{
|
||||||
Method: base.Teardown,
|
Method: base.Teardown,
|
||||||
URL: cc.streamURL,
|
URL: cc.streamBaseURL,
|
||||||
SkipResponse: true,
|
SkipResponse: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -185,10 +185,10 @@ func (cc *ClientConn) reset() {
|
|||||||
|
|
||||||
cc.state = clientConnStateInitial
|
cc.state = clientConnStateInitial
|
||||||
cc.nconn = nil
|
cc.nconn = nil
|
||||||
cc.streamURL = nil
|
cc.streamBaseURL = nil
|
||||||
cc.streamProtocol = nil
|
cc.streamProtocol = nil
|
||||||
cc.tracks = make(map[int]clientConnTrack)
|
cc.tracks = make(map[int]clientConnTrack)
|
||||||
cc.getParameterSupported = false
|
cc.useGetParameter = false
|
||||||
cc.backgroundRunning = false
|
cc.backgroundRunning = false
|
||||||
|
|
||||||
// read
|
// 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}
|
return res, liberrors.ErrClientWrongStatusCode{Code: res.StatusCode, Message: res.StatusMessage}
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.getParameterSupported = func() bool {
|
cc.useGetParameter = func() bool {
|
||||||
pub, ok := res.Header["Public"]
|
pub, ok := res.Header["Public"]
|
||||||
if !ok || len(pub) != 1 {
|
if !ok || len(pub) != 1 {
|
||||||
return false
|
return false
|
||||||
@@ -453,7 +453,29 @@ func (cc *ClientConn) Describe(u *base.URL) (Tracks, *base.Response, error) {
|
|||||||
return nil, nil, liberrors.ErrClientContentTypeUnsupported{CT: ct}
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -481,7 +503,7 @@ func (cc *ClientConn) Setup(mode headers.TransportMode, track *Track,
|
|||||||
return nil, liberrors.ErrClientCannotReadPublishAtSameTime{}
|
return nil, liberrors.ErrClientCannotReadPublishAtSameTime{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cc.streamURL != nil && *track.BaseURL != *cc.streamURL {
|
if cc.streamBaseURL != nil && *track.BaseURL != *cc.streamBaseURL {
|
||||||
return nil, liberrors.ErrClientCannotSetupTracksDifferentURLs{}
|
return nil, liberrors.ErrClientCannotSetupTracksDifferentURLs{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -670,7 +692,7 @@ func (cc *ClientConn) Setup(mode headers.TransportMode, track *Track,
|
|||||||
cct.rtcpSender = rtcpsender.New(clockRate)
|
cct.rtcpSender = rtcpsender.New(clockRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.streamURL = track.BaseURL
|
cc.streamBaseURL = track.BaseURL
|
||||||
cc.streamProtocol = &proto
|
cc.streamProtocol = &proto
|
||||||
|
|
||||||
if proto == StreamProtocolUDP {
|
if proto == StreamProtocolUDP {
|
||||||
@@ -726,7 +748,7 @@ func (cc *ClientConn) Pause() (*base.Response, error) {
|
|||||||
|
|
||||||
res, err := cc.Do(&base.Request{
|
res, err := cc.Do(&base.Request{
|
||||||
Method: base.Pause,
|
Method: base.Pause,
|
||||||
URL: cc.streamURL,
|
URL: cc.streamBaseURL,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -837,23 +859,19 @@ func (cc *ClientConn) ReadFrames(onFrame func(int, StreamType, []byte)) chan err
|
|||||||
safeState == clientConnStatePlay {
|
safeState == clientConnStatePlay {
|
||||||
if _, ok := err.(liberrors.ErrClientNoUDPPacketsRecently); ok {
|
if _, ok := err.(liberrors.ErrClientNoUDPPacketsRecently); ok {
|
||||||
if cc.conf.StreamProtocol == nil {
|
if cc.conf.StreamProtocol == nil {
|
||||||
prevURL := cc.streamURL
|
prevBaseURL := cc.streamBaseURL
|
||||||
|
oldUseGetParameter := cc.useGetParameter
|
||||||
prevTracks := cc.tracks
|
prevTracks := cc.tracks
|
||||||
cc.reset()
|
cc.reset()
|
||||||
v := StreamProtocolTCP
|
v := StreamProtocolTCP
|
||||||
cc.streamProtocol = &v
|
cc.streamProtocol = &v
|
||||||
|
cc.useGetParameter = oldUseGetParameter
|
||||||
|
|
||||||
err := cc.connOpen(prevURL.Scheme, prevURL.Host)
|
err := cc.connOpen(prevBaseURL.Scheme, prevBaseURL.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = cc.Options(prevURL)
|
|
||||||
if err != nil {
|
|
||||||
cc.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, track := range prevTracks {
|
for _, track := range prevTracks {
|
||||||
_, err := cc.Setup(headers.TransportModePlay, track.track, 0, 0)
|
_, err := cc.Setup(headers.TransportModePlay, track.track, 0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -20,11 +20,14 @@ func (cc *ClientConn) Announce(u *base.URL, tracks Tracks) (*base.Response, erro
|
|||||||
return nil, err
|
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
|
// set id, base url and control attribute on tracks
|
||||||
for i, t := range tracks {
|
for i, t := range tracks {
|
||||||
t.ID = i
|
t.ID = i
|
||||||
t.BaseURL = u
|
t.BaseURL = baseURL
|
||||||
|
|
||||||
t.Media.Attributes = append(t.Media.Attributes, psdp.Attribute{
|
t.Media.Attributes = append(t.Media.Attributes, psdp.Attribute{
|
||||||
Key: "control",
|
Key: "control",
|
||||||
Value: "trackID=" + strconv.FormatInt(int64(i), 10),
|
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}
|
Code: res.StatusCode, Message: res.StatusMessage}
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.streamURL = u
|
cc.streamBaseURL = baseURL
|
||||||
cc.state = clientConnStatePreRecord
|
cc.state = clientConnStatePreRecord
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
@@ -66,7 +69,7 @@ func (cc *ClientConn) Record() (*base.Response, error) {
|
|||||||
|
|
||||||
res, err := cc.Do(&base.Request{
|
res, err := cc.Do(&base.Request{
|
||||||
Method: base.Record,
|
Method: base.Record,
|
||||||
URL: cc.streamURL,
|
URL: cc.streamBaseURL,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -40,6 +40,7 @@ func TestClientPublishSerial(t *testing.T) {
|
|||||||
err = req.Read(bconn.Reader)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Options, req.Method)
|
require.Equal(t, base.Options, req.Method)
|
||||||
|
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||||
|
|
||||||
err = base.Response{
|
err = base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
@@ -56,6 +57,7 @@ func TestClientPublishSerial(t *testing.T) {
|
|||||||
err = req.Read(bconn.Reader)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Announce, req.Method)
|
require.Equal(t, base.Announce, req.Method)
|
||||||
|
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||||
|
|
||||||
err = base.Response{
|
err = base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
@@ -65,6 +67,7 @@ func TestClientPublishSerial(t *testing.T) {
|
|||||||
err = req.Read(bconn.Reader)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Setup, req.Method)
|
require.Equal(t, base.Setup, req.Method)
|
||||||
|
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream/trackID=0"), req.URL)
|
||||||
|
|
||||||
var inTH headers.Transport
|
var inTH headers.Transport
|
||||||
err = inTH.Read(req.Header["Transport"])
|
err = inTH.Read(req.Header["Transport"])
|
||||||
@@ -110,6 +113,7 @@ func TestClientPublishSerial(t *testing.T) {
|
|||||||
err = req.Read(bconn.Reader)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Record, req.Method)
|
require.Equal(t, base.Record, req.Method)
|
||||||
|
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||||
|
|
||||||
err = base.Response{
|
err = base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
@@ -151,6 +155,7 @@ func TestClientPublishSerial(t *testing.T) {
|
|||||||
err = req.Read(bconn.Reader)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Teardown, req.Method)
|
require.Equal(t, base.Teardown, req.Method)
|
||||||
|
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream"), req.URL)
|
||||||
|
|
||||||
err = base.Response{
|
err = base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
|
@@ -22,7 +22,7 @@ func (cc *ClientConn) Play() (*base.Response, error) {
|
|||||||
|
|
||||||
res, err := cc.Do(&base.Request{
|
res, err := cc.Do(&base.Request{
|
||||||
Method: base.Play,
|
Method: base.Play,
|
||||||
URL: cc.streamURL,
|
URL: cc.streamBaseURL,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -119,13 +119,13 @@ func (cc *ClientConn) backgroundPlayUDP() error {
|
|||||||
_, err := cc.Do(&base.Request{
|
_, err := cc.Do(&base.Request{
|
||||||
Method: func() base.Method {
|
Method: func() base.Method {
|
||||||
// the vlc integrated rtsp server requires GET_PARAMETER
|
// the vlc integrated rtsp server requires GET_PARAMETER
|
||||||
if cc.getParameterSupported {
|
if cc.useGetParameter {
|
||||||
return base.GetParameter
|
return base.GetParameter
|
||||||
}
|
}
|
||||||
return base.Options
|
return base.Options
|
||||||
}(),
|
}(),
|
||||||
// use the stream path, otherwise some cameras do not reply
|
// use the stream base URL, otherwise some cameras do not reply
|
||||||
URL: cc.streamURL,
|
URL: cc.streamBaseURL,
|
||||||
SkipResponse: true,
|
SkipResponse: true,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -74,6 +74,7 @@ func TestClientRead(t *testing.T) {
|
|||||||
err = req.Read(bconn.Reader)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Options, req.Method)
|
require.Equal(t, base.Options, req.Method)
|
||||||
|
require.Equal(t, base.MustParseURL(scheme+"://localhost:8554/teststream"), req.URL)
|
||||||
|
|
||||||
err = base.Response{
|
err = base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
@@ -90,6 +91,7 @@ func TestClientRead(t *testing.T) {
|
|||||||
err = req.Read(bconn.Reader)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Describe, req.Method)
|
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"))
|
track, err := NewTrackH264(96, []byte("123456"), []byte("123456"))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@@ -98,6 +100,7 @@ func TestClientRead(t *testing.T) {
|
|||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||||
|
"Content-Base": base.HeaderValue{scheme + "://localhost:8554/teststream/"},
|
||||||
},
|
},
|
||||||
Body: Tracks{track}.Write(),
|
Body: Tracks{track}.Write(),
|
||||||
}.Write(bconn.Writer)
|
}.Write(bconn.Writer)
|
||||||
@@ -106,6 +109,7 @@ func TestClientRead(t *testing.T) {
|
|||||||
err = req.Read(bconn.Reader)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Setup, req.Method)
|
require.Equal(t, base.Setup, req.Method)
|
||||||
|
require.Equal(t, base.MustParseURL(scheme+"://localhost:8554/teststream/trackID=0"), req.URL)
|
||||||
|
|
||||||
var inTH headers.Transport
|
var inTH headers.Transport
|
||||||
err = inTH.Read(req.Header["Transport"])
|
err = inTH.Read(req.Header["Transport"])
|
||||||
@@ -150,6 +154,7 @@ func TestClientRead(t *testing.T) {
|
|||||||
err = req.Read(bconn.Reader)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Play, req.Method)
|
require.Equal(t, base.Play, req.Method)
|
||||||
|
require.Equal(t, base.MustParseURL(scheme+"://localhost:8554/teststream/"), req.URL)
|
||||||
|
|
||||||
err = base.Response{
|
err = base.Response{
|
||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
@@ -194,6 +199,16 @@ func TestClientRead(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
close(frameRecv)
|
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{
|
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) {
|
func TestClientReadAnyPort(t *testing.T) {
|
||||||
for _, ca := range []string{
|
for _, ca := range []string{
|
||||||
"zero",
|
"zero",
|
||||||
@@ -274,6 +391,7 @@ func TestClientReadAnyPort(t *testing.T) {
|
|||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||||
|
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||||
},
|
},
|
||||||
Body: Tracks{track}.Write(),
|
Body: Tracks{track}.Write(),
|
||||||
}.Write(bconn.Writer)
|
}.Write(bconn.Writer)
|
||||||
@@ -392,6 +510,7 @@ func TestClientReadAutomaticProtocol(t *testing.T) {
|
|||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||||
|
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||||
},
|
},
|
||||||
Body: Tracks{track}.Write(),
|
Body: Tracks{track}.Write(),
|
||||||
}.Write(bconn.Writer)
|
}.Write(bconn.Writer)
|
||||||
@@ -497,6 +616,7 @@ func TestClientReadAutomaticProtocol(t *testing.T) {
|
|||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||||
|
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||||
},
|
},
|
||||||
Body: Tracks{track}.Write(),
|
Body: Tracks{track}.Write(),
|
||||||
}.Write(bconn.Writer)
|
}.Write(bconn.Writer)
|
||||||
@@ -505,6 +625,7 @@ func TestClientReadAutomaticProtocol(t *testing.T) {
|
|||||||
err = req.Read(bconn.Reader)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Setup, req.Method)
|
require.Equal(t, base.Setup, req.Method)
|
||||||
|
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream/trackID=0"), req.URL)
|
||||||
|
|
||||||
var inTH headers.Transport
|
var inTH headers.Transport
|
||||||
err = inTH.Read(req.Header["Transport"])
|
err = inTH.Read(req.Header["Transport"])
|
||||||
@@ -552,25 +673,10 @@ func TestClientReadAutomaticProtocol(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
bconn = bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
|
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)
|
err = req.Read(bconn.Reader)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, base.Setup, req.Method)
|
require.Equal(t, base.Setup, req.Method)
|
||||||
|
require.Equal(t, base.MustParseURL("rtsp://localhost:8554/teststream/trackID=0"), req.URL)
|
||||||
|
|
||||||
inTH = headers.Transport{}
|
inTH = headers.Transport{}
|
||||||
err = inTH.Read(req.Header["Transport"])
|
err = inTH.Read(req.Header["Transport"])
|
||||||
@@ -715,6 +821,7 @@ func TestClientReadRedirect(t *testing.T) {
|
|||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||||
|
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||||
},
|
},
|
||||||
Body: Tracks{track}.Write(),
|
Body: Tracks{track}.Write(),
|
||||||
}.Write(bconn.Writer)
|
}.Write(bconn.Writer)
|
||||||
@@ -869,6 +976,7 @@ func TestClientReadPause(t *testing.T) {
|
|||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||||
|
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||||
},
|
},
|
||||||
Body: Tracks{track}.Write(),
|
Body: Tracks{track}.Write(),
|
||||||
}.Write(bconn.Writer)
|
}.Write(bconn.Writer)
|
||||||
@@ -1042,6 +1150,7 @@ func TestClientReadRTCPReport(t *testing.T) {
|
|||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||||
|
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||||
},
|
},
|
||||||
Body: Tracks{track}.Write(),
|
Body: Tracks{track}.Write(),
|
||||||
}.Write(bconn.Writer)
|
}.Write(bconn.Writer)
|
||||||
@@ -1214,6 +1323,7 @@ func TestClientReadErrorTimeout(t *testing.T) {
|
|||||||
StatusCode: base.StatusOK,
|
StatusCode: base.StatusOK,
|
||||||
Header: base.Header{
|
Header: base.Header{
|
||||||
"Content-Type": base.HeaderValue{"application/sdp"},
|
"Content-Type": base.HeaderValue{"application/sdp"},
|
||||||
|
"Content-Base": base.HeaderValue{"rtsp://localhost:8554/teststream/"},
|
||||||
},
|
},
|
||||||
Body: Tracks{track}.Write(),
|
Body: Tracks{track}.Write(),
|
||||||
}.Write(bconn.Writer)
|
}.Write(bconn.Writer)
|
||||||
|
@@ -113,16 +113,3 @@ func (u *URL) RTSPPathAndQuery() (string, bool) {
|
|||||||
|
|
||||||
return pathAndQuery, true
|
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)
|
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
|
// control attribute contains a relative control attribute
|
||||||
ur := t.BaseURL.Clone()
|
// insert the control attribute at the end of the url
|
||||||
ur.AddControlAttribute(controlAttr)
|
// 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
|
return ur, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
110
track_test.go
110
track_test.go
@@ -5,6 +5,8 @@ import (
|
|||||||
|
|
||||||
psdp "github.com/pion/sdp/v3"
|
psdp "github.com/pion/sdp/v3"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/aler9/gortsplib/pkg/base"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTrackClockRate(t *testing.T) {
|
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 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")
|
var testH264PPS = []byte("\x68\xee\x3c\x80")
|
||||||
|
Reference in New Issue
Block a user