|
|
|
|
@ -17,7 +17,11 @@ import (
@@ -17,7 +17,11 @@ import (
|
|
|
|
|
"github.com/stretchr/testify/require" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
func whipGetICEServers(t *testing.T, hc *http.Client, ur string) []webrtc.ICEServer { |
|
|
|
|
func whipGetICEServers( |
|
|
|
|
t *testing.T, |
|
|
|
|
hc *http.Client, |
|
|
|
|
ur string, |
|
|
|
|
) []webrtc.ICEServer { |
|
|
|
|
req, err := http.NewRequest("OPTIONS", ur, nil) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
@ -35,7 +39,10 @@ func whipGetICEServers(t *testing.T, hc *http.Client, ur string) []webrtc.ICESer
@@ -35,7 +39,10 @@ func whipGetICEServers(t *testing.T, hc *http.Client, ur string) []webrtc.ICESer
|
|
|
|
|
return servers |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func whipPostOffer(t *testing.T, hc *http.Client, ur string, |
|
|
|
|
func whipPostOffer( |
|
|
|
|
t *testing.T, |
|
|
|
|
hc *http.Client, |
|
|
|
|
ur string, |
|
|
|
|
offer *webrtc.SessionDescription, |
|
|
|
|
) (*webrtc.SessionDescription, string) { |
|
|
|
|
req, err := http.NewRequest("POST", ur, bytes.NewReader([]byte(offer.SDP))) |
|
|
|
|
@ -50,7 +57,11 @@ func whipPostOffer(t *testing.T, hc *http.Client, ur string,
@@ -50,7 +57,11 @@ func whipPostOffer(t *testing.T, hc *http.Client, ur string,
|
|
|
|
|
require.Equal(t, http.StatusCreated, res.StatusCode) |
|
|
|
|
require.Equal(t, "application/sdp", res.Header.Get("Content-Type")) |
|
|
|
|
require.Equal(t, "application/trickle-ice-sdpfrag", res.Header.Get("Accept-Patch")) |
|
|
|
|
require.Equal(t, req.URL.Path, res.Header.Get("Location")) |
|
|
|
|
loc := req.URL.Path |
|
|
|
|
if req.URL.RawQuery != "" { |
|
|
|
|
loc += "?" + req.URL.RawQuery |
|
|
|
|
} |
|
|
|
|
require.Equal(t, loc, res.Header.Get("Location")) |
|
|
|
|
|
|
|
|
|
link, ok := res.Header["Link"] |
|
|
|
|
require.Equal(t, true, ok) |
|
|
|
|
@ -73,8 +84,12 @@ func whipPostOffer(t *testing.T, hc *http.Client, ur string,
@@ -73,8 +84,12 @@ func whipPostOffer(t *testing.T, hc *http.Client, ur string,
|
|
|
|
|
return answer, etag |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func whipPostCandidate(t *testing.T, ur string, offer *webrtc.SessionDescription, |
|
|
|
|
etag string, candidate *webrtc.ICECandidateInit, |
|
|
|
|
func whipPostCandidate( |
|
|
|
|
t *testing.T, |
|
|
|
|
ur string, |
|
|
|
|
offer *webrtc.SessionDescription, |
|
|
|
|
etag string, |
|
|
|
|
candidate *webrtc.ICECandidateInit, |
|
|
|
|
) { |
|
|
|
|
frag, err := marshalICEFragment(offer, []*webrtc.ICECandidateInit{candidate}) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
@ -102,7 +117,12 @@ type webRTCTestClient struct {
@@ -102,7 +117,12 @@ type webRTCTestClient struct {
|
|
|
|
|
closed chan struct{} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func newWebRTCTestClient(t *testing.T, hc *http.Client, ur string, publish bool) *webRTCTestClient { |
|
|
|
|
func newWebRTCTestClient( |
|
|
|
|
t *testing.T, |
|
|
|
|
hc *http.Client, |
|
|
|
|
ur string, |
|
|
|
|
publish bool, |
|
|
|
|
) *webRTCTestClient { |
|
|
|
|
iceServers := whipGetICEServers(t, hc, ur) |
|
|
|
|
|
|
|
|
|
pc, err := webrtc.NewPeerConnection(webrtc.Configuration{ |
|
|
|
|
@ -247,62 +267,119 @@ func (c *webRTCTestClient) close() {
@@ -247,62 +267,119 @@ func (c *webRTCTestClient) close() {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestWebRTCRead(t *testing.T) { |
|
|
|
|
p, ok := newInstance("paths:\n" + |
|
|
|
|
" all:\n") |
|
|
|
|
require.Equal(t, true, ok) |
|
|
|
|
defer p.Close() |
|
|
|
|
|
|
|
|
|
medi := &media.Media{ |
|
|
|
|
Type: media.TypeVideo, |
|
|
|
|
Formats: []formats.Format{&formats.H264{ |
|
|
|
|
PayloadTyp: 96, |
|
|
|
|
PacketizationMode: 1, |
|
|
|
|
}}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
v := gortsplib.TransportTCP |
|
|
|
|
source := gortsplib.Client{ |
|
|
|
|
Transport: &v, |
|
|
|
|
for _, auth := range []string{ |
|
|
|
|
"none", |
|
|
|
|
"internal", |
|
|
|
|
"external", |
|
|
|
|
} { |
|
|
|
|
t.Run("auth_"+auth, func(t *testing.T) { |
|
|
|
|
var conf string |
|
|
|
|
|
|
|
|
|
switch auth { |
|
|
|
|
case "none": |
|
|
|
|
conf = "paths:\n" + |
|
|
|
|
" all:\n" |
|
|
|
|
|
|
|
|
|
case "internal": |
|
|
|
|
conf = "paths:\n" + |
|
|
|
|
" all:\n" + |
|
|
|
|
" readUser: myuser\n" + |
|
|
|
|
" readPass: mypass\n" |
|
|
|
|
|
|
|
|
|
case "external": |
|
|
|
|
conf = "externalAuthenticationURL: http://localhost:9120/auth\n" + |
|
|
|
|
"paths:\n" + |
|
|
|
|
" all:\n" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
p, ok := newInstance(conf) |
|
|
|
|
require.Equal(t, true, ok) |
|
|
|
|
defer p.Close() |
|
|
|
|
|
|
|
|
|
var a *testHTTPAuthenticator |
|
|
|
|
if auth == "external" { |
|
|
|
|
a = newTestHTTPAuthenticator(t, "rtsp", "publish") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
medi := &media.Media{ |
|
|
|
|
Type: media.TypeVideo, |
|
|
|
|
Formats: []formats.Format{&formats.H264{ |
|
|
|
|
PayloadTyp: 96, |
|
|
|
|
PacketizationMode: 1, |
|
|
|
|
}}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
v := gortsplib.TransportTCP |
|
|
|
|
source := gortsplib.Client{ |
|
|
|
|
Transport: &v, |
|
|
|
|
} |
|
|
|
|
err := source.StartRecording( |
|
|
|
|
"rtsp://testpublisher:testpass@localhost:8554/teststream?param=value", media.Medias{medi}) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
defer source.Close() |
|
|
|
|
|
|
|
|
|
if auth == "external" { |
|
|
|
|
a.close() |
|
|
|
|
a = newTestHTTPAuthenticator(t, "webrtc", "read") |
|
|
|
|
defer a.close() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
hc := &http.Client{Transport: &http.Transport{}} |
|
|
|
|
|
|
|
|
|
user := "" |
|
|
|
|
pass := "" |
|
|
|
|
|
|
|
|
|
switch auth { |
|
|
|
|
case "internal": |
|
|
|
|
user = "myuser" |
|
|
|
|
pass = "mypass" |
|
|
|
|
|
|
|
|
|
case "external": |
|
|
|
|
user = "testreader" |
|
|
|
|
pass = "testpass" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ur := "http://" |
|
|
|
|
if user != "" { |
|
|
|
|
ur += user + ":" + pass + "@" |
|
|
|
|
} |
|
|
|
|
ur += "localhost:8889/teststream/whep?param=value" |
|
|
|
|
|
|
|
|
|
c := newWebRTCTestClient(t, hc, ur, false) |
|
|
|
|
defer c.close() |
|
|
|
|
|
|
|
|
|
time.Sleep(500 * time.Millisecond) |
|
|
|
|
|
|
|
|
|
source.WritePacketRTP(medi, &rtp.Packet{ |
|
|
|
|
Header: rtp.Header{ |
|
|
|
|
Version: 2, |
|
|
|
|
Marker: true, |
|
|
|
|
PayloadType: 96, |
|
|
|
|
SequenceNumber: 123, |
|
|
|
|
Timestamp: 45343, |
|
|
|
|
SSRC: 563423, |
|
|
|
|
}, |
|
|
|
|
Payload: []byte{0x01, 0x02, 0x03, 0x04}, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
trak := <-c.incomingTrack |
|
|
|
|
|
|
|
|
|
pkt, _, err := trak.ReadRTP() |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
require.Equal(t, &rtp.Packet{ |
|
|
|
|
Header: rtp.Header{ |
|
|
|
|
Version: 2, |
|
|
|
|
Marker: true, |
|
|
|
|
PayloadType: 102, |
|
|
|
|
SequenceNumber: pkt.SequenceNumber, |
|
|
|
|
Timestamp: pkt.Timestamp, |
|
|
|
|
SSRC: pkt.SSRC, |
|
|
|
|
CSRC: []uint32{}, |
|
|
|
|
}, |
|
|
|
|
Payload: []byte{0x01, 0x02, 0x03, 0x04}, |
|
|
|
|
}, pkt) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
err := source.StartRecording("rtsp://localhost:8554/stream", media.Medias{medi}) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
defer source.Close() |
|
|
|
|
|
|
|
|
|
hc := &http.Client{Transport: &http.Transport{}} |
|
|
|
|
|
|
|
|
|
c := newWebRTCTestClient(t, hc, "http://localhost:8889/stream/whep", false) |
|
|
|
|
defer c.close() |
|
|
|
|
|
|
|
|
|
time.Sleep(500 * time.Millisecond) |
|
|
|
|
|
|
|
|
|
source.WritePacketRTP(medi, &rtp.Packet{ |
|
|
|
|
Header: rtp.Header{ |
|
|
|
|
Version: 2, |
|
|
|
|
Marker: true, |
|
|
|
|
PayloadType: 96, |
|
|
|
|
SequenceNumber: 123, |
|
|
|
|
Timestamp: 45343, |
|
|
|
|
SSRC: 563423, |
|
|
|
|
}, |
|
|
|
|
Payload: []byte{0x01, 0x02, 0x03, 0x04}, |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
trak := <-c.incomingTrack |
|
|
|
|
|
|
|
|
|
pkt, _, err := trak.ReadRTP() |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
require.Equal(t, &rtp.Packet{ |
|
|
|
|
Header: rtp.Header{ |
|
|
|
|
Version: 2, |
|
|
|
|
Marker: true, |
|
|
|
|
PayloadType: 102, |
|
|
|
|
SequenceNumber: pkt.SequenceNumber, |
|
|
|
|
Timestamp: pkt.Timestamp, |
|
|
|
|
SSRC: pkt.SSRC, |
|
|
|
|
CSRC: []uint32{}, |
|
|
|
|
}, |
|
|
|
|
Payload: []byte{0x01, 0x02, 0x03, 0x04}, |
|
|
|
|
}, pkt) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestWebRTCReadNotFound(t *testing.T) { |
|
|
|
|
@ -340,60 +417,133 @@ func TestWebRTCReadNotFound(t *testing.T) {
@@ -340,60 +417,133 @@ func TestWebRTCReadNotFound(t *testing.T) {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestWebRTCPublish(t *testing.T) { |
|
|
|
|
p, ok := newInstance("paths:\n" + |
|
|
|
|
" all:\n") |
|
|
|
|
require.Equal(t, true, ok) |
|
|
|
|
defer p.Close() |
|
|
|
|
|
|
|
|
|
hc := &http.Client{Transport: &http.Transport{}} |
|
|
|
|
|
|
|
|
|
s := newWebRTCTestClient(t, hc, "http://localhost:8889/stream/whip", true) |
|
|
|
|
defer s.close() |
|
|
|
|
|
|
|
|
|
c := gortsplib.Client{ |
|
|
|
|
OnDecodeError: func(err error) { |
|
|
|
|
panic(err) |
|
|
|
|
}, |
|
|
|
|
for _, auth := range []string{ |
|
|
|
|
"none", |
|
|
|
|
"internal", |
|
|
|
|
"external", |
|
|
|
|
} { |
|
|
|
|
t.Run("auth_"+auth, func(t *testing.T) { |
|
|
|
|
var conf string |
|
|
|
|
|
|
|
|
|
switch auth { |
|
|
|
|
case "none": |
|
|
|
|
conf = "paths:\n" + |
|
|
|
|
" all:\n" |
|
|
|
|
|
|
|
|
|
case "internal": |
|
|
|
|
conf = "paths:\n" + |
|
|
|
|
" all:\n" + |
|
|
|
|
" publishUser: myuser\n" + |
|
|
|
|
" publishPass: mypass\n" |
|
|
|
|
|
|
|
|
|
case "external": |
|
|
|
|
conf = "externalAuthenticationURL: http://localhost:9120/auth\n" + |
|
|
|
|
"paths:\n" + |
|
|
|
|
" all:\n" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
p, ok := newInstance(conf) |
|
|
|
|
require.Equal(t, true, ok) |
|
|
|
|
defer p.Close() |
|
|
|
|
|
|
|
|
|
var a *testHTTPAuthenticator |
|
|
|
|
if auth == "external" { |
|
|
|
|
a = newTestHTTPAuthenticator(t, "webrtc", "publish") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
hc := &http.Client{Transport: &http.Transport{}} |
|
|
|
|
|
|
|
|
|
// OPTIONS preflight requests must always work, without authentication
|
|
|
|
|
func() { |
|
|
|
|
req, err := http.NewRequest("OPTIONS", "http://localhost:8889/teststream/whip", nil) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
res, err := hc.Do(req) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
defer res.Body.Close() |
|
|
|
|
|
|
|
|
|
require.Equal(t, http.StatusOK, res.StatusCode) |
|
|
|
|
|
|
|
|
|
if auth != "none" { |
|
|
|
|
_, ok := res.Header["Link"] |
|
|
|
|
require.Equal(t, false, ok) |
|
|
|
|
} |
|
|
|
|
}() |
|
|
|
|
|
|
|
|
|
user := "" |
|
|
|
|
pass := "" |
|
|
|
|
|
|
|
|
|
switch auth { |
|
|
|
|
case "internal": |
|
|
|
|
user = "myuser" |
|
|
|
|
pass = "mypass" |
|
|
|
|
|
|
|
|
|
case "external": |
|
|
|
|
user = "testpublisher" |
|
|
|
|
pass = "testpass" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ur := "http://" |
|
|
|
|
if user != "" { |
|
|
|
|
ur += user + ":" + pass + "@" |
|
|
|
|
} |
|
|
|
|
ur += "localhost:8889/teststream/whip?param=value" |
|
|
|
|
|
|
|
|
|
s := newWebRTCTestClient(t, hc, ur, true) |
|
|
|
|
defer s.close() |
|
|
|
|
|
|
|
|
|
if auth == "external" { |
|
|
|
|
a.close() |
|
|
|
|
a = newTestHTTPAuthenticator(t, "rtsp", "read") |
|
|
|
|
defer a.close() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
c := gortsplib.Client{ |
|
|
|
|
OnDecodeError: func(err error) { |
|
|
|
|
panic(err) |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
u, err := url.Parse("rtsp://testreader:testpass@127.0.0.1:8554/teststream?param=value") |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
err = c.Start(u.Scheme, u.Host) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
defer c.Close() |
|
|
|
|
|
|
|
|
|
medias, baseURL, _, err := c.Describe(u) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
var forma *formats.VP8 |
|
|
|
|
medi := medias.FindFormat(&forma) |
|
|
|
|
|
|
|
|
|
_, err = c.Setup(medi, baseURL, 0, 0) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
received := make(chan struct{}) |
|
|
|
|
|
|
|
|
|
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { |
|
|
|
|
require.Equal(t, []byte{0x05, 0x06, 0x07, 0x08}, pkt.Payload) |
|
|
|
|
close(received) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
_, err = c.Play(nil) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
err = s.outgoingTrack1.WriteRTP(&rtp.Packet{ |
|
|
|
|
Header: rtp.Header{ |
|
|
|
|
Version: 2, |
|
|
|
|
Marker: true, |
|
|
|
|
PayloadType: 96, |
|
|
|
|
SequenceNumber: 124, |
|
|
|
|
Timestamp: 45343, |
|
|
|
|
SSRC: 563423, |
|
|
|
|
}, |
|
|
|
|
Payload: []byte{0x05, 0x06, 0x07, 0x08}, |
|
|
|
|
}) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
<-received |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
u, err := url.Parse("rtsp://127.0.0.1:8554/stream") |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
err = c.Start(u.Scheme, u.Host) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
defer c.Close() |
|
|
|
|
|
|
|
|
|
medias, baseURL, _, err := c.Describe(u) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
var forma *formats.VP8 |
|
|
|
|
medi := medias.FindFormat(&forma) |
|
|
|
|
|
|
|
|
|
_, err = c.Setup(medi, baseURL, 0, 0) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
received := make(chan struct{}) |
|
|
|
|
|
|
|
|
|
c.OnPacketRTP(medi, forma, func(pkt *rtp.Packet) { |
|
|
|
|
require.Equal(t, []byte{0x05, 0x06, 0x07, 0x08}, pkt.Payload) |
|
|
|
|
close(received) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
_, err = c.Play(nil) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
err = s.outgoingTrack1.WriteRTP(&rtp.Packet{ |
|
|
|
|
Header: rtp.Header{ |
|
|
|
|
Version: 2, |
|
|
|
|
Marker: true, |
|
|
|
|
PayloadType: 96, |
|
|
|
|
SequenceNumber: 124, |
|
|
|
|
Timestamp: 45343, |
|
|
|
|
SSRC: 563423, |
|
|
|
|
}, |
|
|
|
|
Payload: []byte{0x05, 0x06, 0x07, 0x08}, |
|
|
|
|
}) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
<-received |
|
|
|
|
} |
|
|
|
|
|