mirror of
https://github.com/aler9/gortsplib
synced 2025-10-06 07:37:07 +08:00
add URL utils
This commit is contained in:
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func mustParseUrl(s string) *url.URL {
|
func urlMustParse(s string) *url.URL {
|
||||||
u, err := url.Parse(s)
|
u, err := url.Parse(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@@ -31,7 +31,7 @@ var casesRequest = []struct {
|
|||||||
"\r\n"),
|
"\r\n"),
|
||||||
Request{
|
Request{
|
||||||
Method: "OPTIONS",
|
Method: "OPTIONS",
|
||||||
Url: mustParseUrl("rtsp://example.com/media.mp4"),
|
Url: urlMustParse("rtsp://example.com/media.mp4"),
|
||||||
Header: Header{
|
Header: Header{
|
||||||
"CSeq": HeaderValue{"1"},
|
"CSeq": HeaderValue{"1"},
|
||||||
"Require": HeaderValue{"implicit-play"},
|
"Require": HeaderValue{"implicit-play"},
|
||||||
@@ -47,7 +47,7 @@ var casesRequest = []struct {
|
|||||||
"\r\n"),
|
"\r\n"),
|
||||||
Request{
|
Request{
|
||||||
Method: "DESCRIBE",
|
Method: "DESCRIBE",
|
||||||
Url: mustParseUrl("rtsp://example.com/media.mp4"),
|
Url: urlMustParse("rtsp://example.com/media.mp4"),
|
||||||
Header: Header{
|
Header: Header{
|
||||||
"Accept": HeaderValue{"application/sdp"},
|
"Accept": HeaderValue{"application/sdp"},
|
||||||
"CSeq": HeaderValue{"2"},
|
"CSeq": HeaderValue{"2"},
|
||||||
@@ -55,14 +55,14 @@ var casesRequest = []struct {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"describe with exclamation mark",
|
"describe with special chars",
|
||||||
[]byte("DESCRIBE rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp RTSP/1.0\r\n" +
|
[]byte("DESCRIBE rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp RTSP/1.0\r\n" +
|
||||||
"Accept: application/sdp\r\n" +
|
"Accept: application/sdp\r\n" +
|
||||||
"CSeq: 3\r\n" +
|
"CSeq: 3\r\n" +
|
||||||
"\r\n"),
|
"\r\n"),
|
||||||
Request{
|
Request{
|
||||||
Method: "DESCRIBE",
|
Method: "DESCRIBE",
|
||||||
Url: mustParseUrl("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
|
Url: urlMustParse("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
|
||||||
Header: Header{
|
Header: Header{
|
||||||
"Accept": HeaderValue{"application/sdp"},
|
"Accept": HeaderValue{"application/sdp"},
|
||||||
"CSeq": HeaderValue{"3"},
|
"CSeq": HeaderValue{"3"},
|
||||||
@@ -91,7 +91,7 @@ var casesRequest = []struct {
|
|||||||
"m=video 2232 RTP/AVP 31\n"),
|
"m=video 2232 RTP/AVP 31\n"),
|
||||||
Request{
|
Request{
|
||||||
Method: "ANNOUNCE",
|
Method: "ANNOUNCE",
|
||||||
Url: mustParseUrl("rtsp://example.com/media.mp4"),
|
Url: urlMustParse("rtsp://example.com/media.mp4"),
|
||||||
Header: Header{
|
Header: Header{
|
||||||
"CSeq": HeaderValue{"7"},
|
"CSeq": HeaderValue{"7"},
|
||||||
"Date": HeaderValue{"23 Jan 1997 15:35:06 GMT"},
|
"Date": HeaderValue{"23 Jan 1997 15:35:06 GMT"},
|
||||||
@@ -125,7 +125,7 @@ var casesRequest = []struct {
|
|||||||
"jitter\n"),
|
"jitter\n"),
|
||||||
Request{
|
Request{
|
||||||
Method: "GET_PARAMETER",
|
Method: "GET_PARAMETER",
|
||||||
Url: mustParseUrl("rtsp://example.com/media.mp4"),
|
Url: urlMustParse("rtsp://example.com/media.mp4"),
|
||||||
Header: Header{
|
Header: Header{
|
||||||
"CSeq": HeaderValue{"9"},
|
"CSeq": HeaderValue{"9"},
|
||||||
"Content-Type": HeaderValue{"text/parameters"},
|
"Content-Type": HeaderValue{"text/parameters"},
|
||||||
|
88
base/url.go
Normal file
88
base/url.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
func stringsReverseIndexByte(s string, c byte) int {
|
||||||
|
for i := len(s) - 1; i >= 0; i-- {
|
||||||
|
if s[i] == c {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLGetBasePath returns the base path of a RTSP URL.
|
||||||
|
// We assume that the URL doesn't contain a control path.
|
||||||
|
func URLGetBasePath(u *url.URL) string {
|
||||||
|
if u.RawPath != "" {
|
||||||
|
return u.RawPath[1:] // remove leading slash
|
||||||
|
}
|
||||||
|
return u.Path[1:] // remove leading slash
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLGetBaseControlPath returns the base path and the control path of a RTSP URL.
|
||||||
|
// We assume that the URL contains a control path.
|
||||||
|
func URLGetBaseControlPath(u *url.URL) (string, string, bool) {
|
||||||
|
pathAndQuery := ""
|
||||||
|
if u.RawPath != "" {
|
||||||
|
pathAndQuery += u.RawPath
|
||||||
|
} else {
|
||||||
|
pathAndQuery += u.Path
|
||||||
|
}
|
||||||
|
if u.RawQuery != "" {
|
||||||
|
pathAndQuery += "?" + u.RawQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
pathAndQuery = pathAndQuery[1:] // remove leading slash
|
||||||
|
|
||||||
|
pos := stringsReverseIndexByte(pathAndQuery, '/')
|
||||||
|
if pos < 0 {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
basePath := pathAndQuery[:pos]
|
||||||
|
|
||||||
|
// remove query from basePath
|
||||||
|
i := strings.IndexByte(basePath, '?')
|
||||||
|
if i >= 0 {
|
||||||
|
basePath = basePath[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(basePath) == 0 {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
controlPath := pathAndQuery[pos+1:]
|
||||||
|
if len(controlPath) == 0 {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
return basePath, controlPath, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// URLAddControlPath adds a control path to a RTSP url.
|
||||||
|
func URLAddControlPath(u *url.URL, controlPath string) {
|
||||||
|
// always insert the control path at the end of the url
|
||||||
|
if u.RawQuery != "" {
|
||||||
|
if !strings.HasSuffix(u.RawQuery, "/") {
|
||||||
|
u.RawQuery += "/"
|
||||||
|
}
|
||||||
|
u.RawQuery += controlPath
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if u.RawPath != "" {
|
||||||
|
if !strings.HasSuffix(u.RawPath, "/") {
|
||||||
|
u.RawPath += "/"
|
||||||
|
}
|
||||||
|
u.RawPath += controlPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(u.Path, "/") {
|
||||||
|
u.Path += "/"
|
||||||
|
}
|
||||||
|
u.Path += controlPath
|
||||||
|
}
|
||||||
|
}
|
109
base/url_test.go
Normal file
109
base/url_test.go
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestURLGetBasePath(t *testing.T) {
|
||||||
|
for _, ca := range []struct{
|
||||||
|
u *url.URL
|
||||||
|
b string
|
||||||
|
} {
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://localhost:8554/teststream"),
|
||||||
|
"teststream",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://localhost:8554/test/stream"),
|
||||||
|
"test/stream",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp"),
|
||||||
|
"test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
|
||||||
|
"te!st",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
|
||||||
|
"user=tmp&password=BagRep1!&channel=1&stream=0.sdp",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
b := URLGetBasePath(ca.u)
|
||||||
|
require.Equal(t, ca.b, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLGetBaseControlPath(t *testing.T) {
|
||||||
|
for _, ca := range []struct{
|
||||||
|
u *url.URL
|
||||||
|
b string
|
||||||
|
c string
|
||||||
|
} {
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://localhost:8554/teststream/trackID=1"),
|
||||||
|
"teststream",
|
||||||
|
"trackID=1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://localhost:8554/test/stream/trackID=1"),
|
||||||
|
"test/stream",
|
||||||
|
"trackID=1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp/trackID=1"),
|
||||||
|
"test",
|
||||||
|
"trackID=1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=1"),
|
||||||
|
"te!st",
|
||||||
|
"trackID=1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=1"),
|
||||||
|
"user=tmp&password=BagRep1!&channel=1&stream=0.sdp",
|
||||||
|
"trackID=1",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
b, c, ok := URLGetBaseControlPath(ca.u)
|
||||||
|
require.Equal(t, true, ok)
|
||||||
|
require.Equal(t, ca.b, b)
|
||||||
|
require.Equal(t, ca.c, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestURLAddControlPath(t *testing.T) {
|
||||||
|
for _, ca := range []struct{
|
||||||
|
u *url.URL
|
||||||
|
ou *url.URL
|
||||||
|
} {
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://localhost:8554/teststream"),
|
||||||
|
urlMustParse("rtsp://localhost:8554/teststream/trackID=1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://localhost:8554/test/stream"),
|
||||||
|
urlMustParse("rtsp://localhost:8554/test/stream/trackID=1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp"),
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/test?user=tmp&password=BagRep1&channel=1&stream=0.sdp/trackID=1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/te!st?user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp"),
|
||||||
|
urlMustParse("rtsp://192.168.1.99:554/user=tmp&password=BagRep1!&channel=1&stream=0.sdp/trackID=1"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
URLAddControlPath(ca.u, "trackID=1")
|
||||||
|
require.Equal(t, ca.ou, ca.u)
|
||||||
|
}
|
||||||
|
}
|
@@ -464,26 +464,7 @@ func (c *ConnClient) urlForTrack(baseUrl *url.URL, mode TransportMode, track *Tr
|
|||||||
RawPath: baseUrl.RawPath,
|
RawPath: baseUrl.RawPath,
|
||||||
RawQuery: baseUrl.RawQuery,
|
RawQuery: baseUrl.RawQuery,
|
||||||
}
|
}
|
||||||
|
base.URLAddControlPath(u, control)
|
||||||
// insert the control at the end of the url
|
|
||||||
if u.RawQuery != "" {
|
|
||||||
if !strings.HasSuffix(u.RawQuery, "/") {
|
|
||||||
u.RawQuery += "/"
|
|
||||||
}
|
|
||||||
u.RawQuery += control
|
|
||||||
|
|
||||||
} else if u.RawPath != "" {
|
|
||||||
if !strings.HasSuffix(u.RawPath, "/") {
|
|
||||||
u.RawPath += "/"
|
|
||||||
}
|
|
||||||
u.RawPath += control
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if !strings.HasSuffix(u.Path, "/") {
|
|
||||||
u.Path += "/"
|
|
||||||
}
|
|
||||||
u.Path += control
|
|
||||||
}
|
|
||||||
return u
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@ type reportReq struct {
|
|||||||
res chan []byte
|
res chan []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// RtcpReceiver is an object that helps building RTCP receiver reports, by parsing
|
// RtcpReceiver allows building RTCP receiver reports, by parsing
|
||||||
// incoming frames.
|
// incoming frames.
|
||||||
type RtcpReceiver struct {
|
type RtcpReceiver struct {
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
Reference in New Issue
Block a user