//nolint:dupl,lll package core import ( "bufio" "bytes" "context" "crypto/tls" "encoding/json" "io" "net" "net/http" "net/url" "os" "testing" "time" "github.com/bluenviron/gortsplib/v4" "github.com/bluenviron/gortsplib/v4/pkg/description" "github.com/bluenviron/mediacommon/pkg/formats/mpegts" srt "github.com/datarhei/gosrt" "github.com/google/uuid" "github.com/pion/rtp" "github.com/stretchr/testify/require" "github.com/bluenviron/mediamtx/internal/protocols/rtmp" "github.com/bluenviron/mediamtx/internal/protocols/webrtc" "github.com/bluenviron/mediamtx/internal/test" ) func checkClose(t *testing.T, closeFunc func() error) { require.NoError(t, closeFunc()) } func httpRequest(t *testing.T, hc *http.Client, method string, ur string, in interface{}, out interface{}) { buf := func() io.Reader { if in == nil { return nil } byts, err := json.Marshal(in) require.NoError(t, err) return bytes.NewBuffer(byts) }() req, err := http.NewRequest(method, ur, buf) require.NoError(t, err) res, err := hc.Do(req) require.NoError(t, err) defer res.Body.Close() if res.StatusCode != http.StatusOK { t.Errorf("bad status code: %d", res.StatusCode) } if out == nil { return } err = json.NewDecoder(res.Body).Decode(out) require.NoError(t, err) } func checkError(t *testing.T, msg string, body io.Reader) { var resErr map[string]interface{} err := json.NewDecoder(body).Decode(&resErr) require.NoError(t, err) require.Equal(t, map[string]interface{}{"error": msg}, resErr) } func TestAPIPathsList(t *testing.T) { type pathSource struct { Type string `json:"type"` } type path struct { Name string `json:"name"` Source pathSource `json:"source"` Ready bool `json:"ready"` Tracks []string `json:"tracks"` BytesReceived uint64 `json:"bytesReceived"` BytesSent uint64 `json:"bytesSent"` } type pathList struct { ItemCount int `json:"itemCount"` PageCount int `json:"pageCount"` Items []path `json:"items"` } t.Run("rtsp session", func(t *testing.T) { p, ok := newInstance("api: yes\n" + "paths:\n" + " mypath:\n") require.Equal(t, true, ok) defer p.Close() tr := &http.Transport{} defer tr.CloseIdleConnections() hc := &http.Client{Transport: tr} media0 := test.UniqueMediaH264() source := gortsplib.Client{} err := source.StartRecording( "rtsp://localhost:8554/mypath", &description.Session{Medias: []*description.Media{ media0, test.MediaMPEG4Audio, }}) require.NoError(t, err) defer source.Close() err = source.WritePacketRTP(media0, &rtp.Packet{ Header: rtp.Header{ Version: 2, PayloadType: 96, }, Payload: []byte{5, 1, 2, 3, 4}, }) require.NoError(t, err) var out pathList httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/list", nil, &out) require.Equal(t, pathList{ ItemCount: 1, PageCount: 1, Items: []path{{ Name: "mypath", Source: pathSource{ Type: "rtspSession", }, Ready: true, Tracks: []string{"H264", "MPEG-4 Audio"}, BytesReceived: 17, }}, }, out) }) t.Run("rtsps session", func(t *testing.T) { serverCertFpath, err := test.CreateTempFile(test.TLSCertPub) require.NoError(t, err) defer os.Remove(serverCertFpath) serverKeyFpath, err := test.CreateTempFile(test.TLSCertKey) require.NoError(t, err) defer os.Remove(serverKeyFpath) p, ok := newInstance("api: yes\n" + "encryption: optional\n" + "serverCert: " + serverCertFpath + "\n" + "serverKey: " + serverKeyFpath + "\n" + "paths:\n" + " mypath:\n") require.Equal(t, true, ok) defer p.Close() tr := &http.Transport{} defer tr.CloseIdleConnections() hc := &http.Client{Transport: tr} source := gortsplib.Client{TLSConfig: &tls.Config{InsecureSkipVerify: true}} err = source.StartRecording("rtsps://localhost:8322/mypath", &description.Session{Medias: []*description.Media{ test.UniqueMediaH264(), test.UniqueMediaMPEG4Audio(), }}) require.NoError(t, err) defer source.Close() var out pathList httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/list", nil, &out) require.Equal(t, pathList{ ItemCount: 1, PageCount: 1, Items: []path{{ Name: "mypath", Source: pathSource{ Type: "rtspsSession", }, Ready: true, Tracks: []string{"H264", "MPEG-4 Audio"}, }}, }, out) }) t.Run("rtsp source", func(t *testing.T) { p, ok := newInstance("api: yes\n" + "paths:\n" + " mypath:\n" + " source: rtsp://127.0.0.1:1234/mypath\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) defer p.Close() tr := &http.Transport{} defer tr.CloseIdleConnections() hc := &http.Client{Transport: tr} var out pathList httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/list", nil, &out) require.Equal(t, pathList{ ItemCount: 1, PageCount: 1, Items: []path{{ Name: "mypath", Source: pathSource{ Type: "rtspSource", }, Ready: false, Tracks: []string{}, }}, }, out) }) t.Run("rtmp source", func(t *testing.T) { p, ok := newInstance("api: yes\n" + "paths:\n" + " mypath:\n" + " source: rtmp://127.0.0.1:1234/mypath\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) defer p.Close() tr := &http.Transport{} defer tr.CloseIdleConnections() hc := &http.Client{Transport: tr} var out pathList httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/list", nil, &out) require.Equal(t, pathList{ ItemCount: 1, PageCount: 1, Items: []path{{ Name: "mypath", Source: pathSource{ Type: "rtmpSource", }, Ready: false, Tracks: []string{}, }}, }, out) }) t.Run("hls source", func(t *testing.T) { p, ok := newInstance("api: yes\n" + "paths:\n" + " mypath:\n" + " source: http://127.0.0.1:1234/mypath\n" + " sourceOnDemand: yes\n") require.Equal(t, true, ok) defer p.Close() tr := &http.Transport{} defer tr.CloseIdleConnections() hc := &http.Client{Transport: tr} var out pathList httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/list", nil, &out) require.Equal(t, pathList{ ItemCount: 1, PageCount: 1, Items: []path{{ Name: "mypath", Source: pathSource{ Type: "hlsSource", }, Ready: false, Tracks: []string{}, }}, }, out) }) } func TestAPIPathsGet(t *testing.T) { p, ok := newInstance("api: yes\n" + "paths:\n" + " all_others:\n") require.Equal(t, true, ok) defer p.Close() tr := &http.Transport{} defer tr.CloseIdleConnections() hc := &http.Client{Transport: tr} for _, ca := range []string{"ok", "ok-nested", "not found"} { t.Run(ca, func(t *testing.T) { type pathSource struct { Type string `json:"type"` } type path struct { Name string `json:"name"` Source pathSource `json:"source"` Ready bool `json:"Ready"` Tracks []string `json:"tracks"` BytesReceived uint64 `json:"bytesReceived"` BytesSent uint64 `json:"bytesSent"` } var pathName string switch ca { case "ok": pathName = "mypath" case "ok-nested": pathName = "my/nested/path" case "not found": pathName = "nonexisting" } if ca == "ok" || ca == "ok-nested" { source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/"+pathName, &description.Session{Medias: []*description.Media{test.UniqueMediaH264()}}) require.NoError(t, err) defer source.Close() var out path httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/paths/get/"+pathName, nil, &out) require.Equal(t, path{ Name: pathName, Source: pathSource{ Type: "rtspSession", }, Ready: true, Tracks: []string{"H264"}, }, out) } else { res, err := hc.Get("http://localhost:9997/v3/paths/get/" + pathName) require.NoError(t, err) defer res.Body.Close() require.Equal(t, http.StatusNotFound, res.StatusCode) checkError(t, "path not found", res.Body) } }) } } func TestAPIProtocolListGet(t *testing.T) { serverCertFpath, err := test.CreateTempFile(test.TLSCertPub) require.NoError(t, err) defer os.Remove(serverCertFpath) serverKeyFpath, err := test.CreateTempFile(test.TLSCertKey) require.NoError(t, err) defer os.Remove(serverKeyFpath) for _, ca := range []string{ "rtsp conns", "rtsp sessions", "rtsps conns", "rtsps sessions", "rtmp", "rtmps", "hls", "webrtc", "srt", } { t.Run(ca, func(t *testing.T) { conf := "api: yes\n" switch ca { case "rtsps conns", "rtsps sessions": conf += "protocols: [tcp]\n" + "encryption: strict\n" + "serverCert: " + serverCertFpath + "\n" + "serverKey: " + serverKeyFpath + "\n" case "rtmps": conf += "rtmpEncryption: strict\n" + "rtmpServerCert: " + serverCertFpath + "\n" + "rtmpServerKey: " + serverKeyFpath + "\n" } conf += "paths:\n" + " all_others:\n" p, ok := newInstance(conf) require.Equal(t, true, ok) defer p.Close() tr := &http.Transport{} defer tr.CloseIdleConnections() hc := &http.Client{Transport: tr} medi := test.UniqueMediaH264() switch ca { //nolint:dupl case "rtsp conns", "rtsp sessions": source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/mypath?key=val", &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() case "rtsps conns", "rtsps sessions": source := gortsplib.Client{ TLSConfig: &tls.Config{InsecureSkipVerify: true}, } err := source.StartRecording("rtsps://localhost:8322/mypath?key=val", &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() case "rtmp", "rtmps": var port string if ca == "rtmp" { port = "1935" } else { port = "1936" } u, err := url.Parse("rtmp://127.0.0.1:" + port + "/mypath?key=val") require.NoError(t, err) nconn, err := func() (net.Conn, error) { if ca == "rtmp" { return net.Dial("tcp", u.Host) } return tls.Dial("tcp", u.Host, &tls.Config{InsecureSkipVerify: true}) }() require.NoError(t, err) defer nconn.Close() conn, err := rtmp.NewClientConn(nconn, u, true) require.NoError(t, err) _, err = rtmp.NewWriter(conn, test.FormatH264, nil) require.NoError(t, err) time.Sleep(500 * time.Millisecond) case "hls": source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/mypath", &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() go func() { time.Sleep(500 * time.Millisecond) for i := 0; i < 3; i++ { /*source.WritePacketRTP(medi, &rtp.Packet{ Header: rtp.Header{ Version: 2, Marker: true, PayloadType: 96, SequenceNumber: 123 + uint16(i), Timestamp: 45343 + uint32(i)*90000, SSRC: 563423, }, Payload: []byte{ testSPS, 0x05, }, }) []byte{ // 1920x1080 baseline 0x67, 0x42, 0xc0, 0x28, 0xd9, 0x00, 0x78, 0x02, 0x27, 0xe5, 0x84, 0x00, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x03, 0x00, 0xf0, 0x3c, 0x60, 0xc9, 0x20, },*/ err := source.WritePacketRTP(medi, &rtp.Packet{ Header: rtp.Header{ Version: 2, Marker: true, PayloadType: 96, SequenceNumber: 123 + uint16(i), Timestamp: 45343 + uint32(i)*90000, SSRC: 563423, }, Payload: []byte{ // testSPS, 0x05, }, }) require.NoError(t, err) } }() func() { res, err := hc.Get("http://localhost:8888/mypath/index.m3u8") require.NoError(t, err) defer res.Body.Close() require.Equal(t, 200, res.StatusCode) }() case "webrtc": source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/mypath", &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() u, err := url.Parse("http://localhost:8889/mypath/whep?key=val") require.NoError(t, err) go func() { time.Sleep(500 * time.Millisecond) err2 := source.WritePacketRTP(medi, &rtp.Packet{ Header: rtp.Header{ Version: 2, Marker: true, PayloadType: 96, SequenceNumber: 123, Timestamp: 45343, SSRC: 563423, }, Payload: []byte{5, 1, 2, 3, 4}, }) require.NoError(t, err2) }() c := &webrtc.WHIPClient{ HTTPClient: hc, URL: u, Log: test.NilLogger, } _, err = c.Read(context.Background()) require.NoError(t, err) defer checkClose(t, c.Close) case "srt": conf := srt.DefaultConfig() conf.StreamId = "publish:mypath:::key=val" conn, err := srt.Dial("srt", "localhost:8890", conf) require.NoError(t, err) defer conn.Close() track := &mpegts.Track{ Codec: &mpegts.CodecH264{}, } bw := bufio.NewWriter(conn) w := mpegts.NewWriter(bw, []*mpegts.Track{track}) require.NoError(t, err) err = w.WriteH26x(track, 0, 0, true, [][]byte{{1}}) require.NoError(t, err) err = bw.Flush() require.NoError(t, err) time.Sleep(500 * time.Millisecond) } var pa string switch ca { case "rtsp conns": pa = "rtspconns" case "rtsp sessions": pa = "rtspsessions" case "rtsps conns": pa = "rtspsconns" case "rtsps sessions": pa = "rtspssessions" case "rtmp": pa = "rtmpconns" case "rtmps": pa = "rtmpsconns" case "hls": pa = "hlsmuxers" case "webrtc": pa = "webrtcsessions" case "srt": pa = "srtconns" } var out1 interface{} httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/list", nil, &out1) switch ca { case "rtsp conns": require.Equal(t, map[string]interface{}{ "pageCount": float64(1), "itemCount": float64(1), "items": []interface{}{ map[string]interface{}{ "bytesReceived": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesReceived"], "bytesSent": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"], "created": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"], "id": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"], "remoteAddr": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"], }, }, }, out1) case "rtsp sessions": require.Equal(t, map[string]interface{}{ "pageCount": float64(1), "itemCount": float64(1), "items": []interface{}{ map[string]interface{}{ "bytesReceived": float64(0), "bytesSent": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"], "created": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"], "id": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"], "path": "mypath", "query": "key=val", "remoteAddr": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"], "state": "publish", "transport": "UDP", }, }, }, out1) case "rtsps conns": require.Equal(t, map[string]interface{}{ "pageCount": float64(1), "itemCount": float64(1), "items": []interface{}{ map[string]interface{}{ "bytesReceived": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesReceived"], "bytesSent": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"], "created": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"], "id": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"], "remoteAddr": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"], }, }, }, out1) case "rtsps sessions": require.Equal(t, map[string]interface{}{ "pageCount": float64(1), "itemCount": float64(1), "items": []interface{}{ map[string]interface{}{ "bytesReceived": float64(0), "bytesSent": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"], "created": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"], "id": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"], "path": "mypath", "query": "key=val", "remoteAddr": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"], "state": "publish", "transport": "TCP", }, }, }, out1) case "rtmp": require.Equal(t, map[string]interface{}{ "pageCount": float64(1), "itemCount": float64(1), "items": []interface{}{ map[string]interface{}{ "bytesReceived": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesReceived"], "bytesSent": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"], "created": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"], "id": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"], "path": "mypath", "query": "key=val", "remoteAddr": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"], "state": "publish", }, }, }, out1) case "rtmps": require.Equal(t, map[string]interface{}{ "pageCount": float64(1), "itemCount": float64(1), "items": []interface{}{ map[string]interface{}{ "bytesReceived": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesReceived"], "bytesSent": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"], "created": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"], "id": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"], "path": "mypath", "query": "key=val", "remoteAddr": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"], "state": "publish", }, }, }, out1) case "hls": require.Equal(t, map[string]interface{}{ "itemCount": float64(1), "pageCount": float64(1), "items": []interface{}{ map[string]interface{}{ "bytesSent": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"], "created": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"], "lastRequest": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["lastRequest"], "path": "mypath", }, }, }, out1) case "webrtc": require.Equal(t, map[string]interface{}{ "itemCount": float64(1), "pageCount": float64(1), "items": []interface{}{ map[string]interface{}{ "bytesReceived": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesReceived"], "bytesSent": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["bytesSent"], "created": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"], "id": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"], "localCandidate": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["localCandidate"], "path": "mypath", "peerConnectionEstablished": true, "query": "key=val", "remoteAddr": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"], "remoteCandidate": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteCandidate"], "state": "read", }, }, }, out1) case "srt": require.Equal(t, map[string]interface{}{ "itemCount": float64(1), "pageCount": float64(1), "items": []interface{}{ map[string]interface{}{ "byteMSS": float64(1500), "bytesAvailReceiveBuf": float64(0), "bytesAvailSendBuf": float64(0), "bytesReceiveBuf": float64(0), "bytesReceived": float64(628), "bytesReceivedBelated": float64(0), "bytesReceivedDrop": float64(0), "bytesReceivedLoss": float64(0), "bytesReceivedRetrans": float64(0), "bytesReceivedUndecrypt": float64(0), "bytesReceivedUnique": float64(628), "bytesRetrans": float64(0), "bytesSendBuf": float64(0), "bytesSendDrop": float64(0), "bytesSent": float64(0), "bytesSentUnique": float64(0), "created": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["created"], "id": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"], "mbpsLinkCapacity": float64(0), "mbpsMaxBW": float64(-1), "mbpsReceiveRate": float64(0), "mbpsSendRate": float64(0), "msRTT": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["msRTT"], "msReceiveBuf": float64(0), "msReceiveTsbPdDelay": float64(120), "msSendBuf": float64(0), "msSendTsbPdDelay": float64(120), "packetsFlightSize": float64(0), "packetsFlowWindow": float64(25600), "packetsReceiveBuf": float64(0), "packetsReceived": float64(1), "packetsReceivedACK": float64(0), "packetsReceivedAvgBelatedTime": float64(0), "packetsReceivedBelated": float64(0), "packetsReceivedDrop": float64(0), "packetsReceivedKM": float64(0), "packetsReceivedLoss": float64(0), "packetsReceivedLossRate": float64(0), "packetsReceivedNAK": float64(0), "packetsReceivedRetrans": float64(0), "packetsReceivedUndecrypt": float64(0), "packetsReceivedUnique": float64(1), "packetsReorderTolerance": float64(0), "packetsRetrans": float64(0), "packetsSendBuf": float64(0), "packetsSendDrop": float64(0), "packetsSendLoss": float64(0), "packetsSendLossRate": float64(0), "packetsSent": float64(0), "packetsSentACK": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["packetsSentACK"], "packetsSentKM": float64(0), "packetsSentNAK": float64(0), "packetsSentUnique": float64(0), "path": "mypath", "query": "key=val", "remoteAddr": out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["remoteAddr"], "state": "publish", "usPacketsSendPeriod": float64(10.967254638671875), "usSndDuration": float64(0), }, }, }, out1) } var out2 interface{} if ca == "hls" { httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/get/"+ out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["path"].(string), nil, &out2) } else { httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/get/"+ out1.(map[string]interface{})["items"].([]interface{})[0].(map[string]interface{})["id"].(string), nil, &out2) } require.Equal(t, out1.(map[string]interface{})["items"].([]interface{})[0], out2) }) } } func TestAPIProtocolGetNotFound(t *testing.T) { serverCertFpath, err := test.CreateTempFile(test.TLSCertPub) require.NoError(t, err) defer os.Remove(serverCertFpath) serverKeyFpath, err := test.CreateTempFile(test.TLSCertKey) require.NoError(t, err) defer os.Remove(serverKeyFpath) for _, ca := range []string{ "rtsp conns", "rtsp sessions", "rtsps conns", "rtsps sessions", "rtmp", "rtmps", "hls", "webrtc", "srt", } { t.Run(ca, func(t *testing.T) { conf := "api: yes\n" switch ca { case "rtsps conns", "rtsps sessions": conf += "protocols: [tcp]\n" + "encryption: strict\n" + "serverCert: " + serverCertFpath + "\n" + "serverKey: " + serverKeyFpath + "\n" case "rtmps": conf += "rtmpEncryption: strict\n" + "rtmpServerCert: " + serverCertFpath + "\n" + "rtmpServerKey: " + serverKeyFpath + "\n" } conf += "paths:\n" + " all_others:\n" p, ok := newInstance(conf) require.Equal(t, true, ok) defer p.Close() tr := &http.Transport{} defer tr.CloseIdleConnections() hc := &http.Client{Transport: tr} var pa string switch ca { case "rtsp conns": pa = "rtspconns" case "rtsp sessions": pa = "rtspsessions" case "rtsps conns": pa = "rtspsconns" case "rtsps sessions": pa = "rtspssessions" case "rtmp": pa = "rtmpconns" case "rtmps": pa = "rtmpsconns" case "hls": pa = "hlsmuxers" case "webrtc": pa = "webrtcsessions" case "srt": pa = "srtconns" } func() { req, err := http.NewRequest(http.MethodGet, "http://localhost:9997/v3/"+pa+"/get/"+uuid.New().String(), nil) require.NoError(t, err) res, err := hc.Do(req) require.NoError(t, err) defer res.Body.Close() require.Equal(t, http.StatusNotFound, res.StatusCode) switch ca { case "rtsp conns", "rtsps conns", "rtmp", "rtmps", "srt": checkError(t, "connection not found", res.Body) case "rtsp sessions", "rtsps sessions", "webrtc": checkError(t, "session not found", res.Body) case "hls": checkError(t, "muxer not found", res.Body) } }() }) } } func TestAPIProtocolKick(t *testing.T) { serverCertFpath, err := test.CreateTempFile(test.TLSCertPub) require.NoError(t, err) defer os.Remove(serverCertFpath) serverKeyFpath, err := test.CreateTempFile(test.TLSCertKey) require.NoError(t, err) defer os.Remove(serverKeyFpath) for _, ca := range []string{ "rtsp", "rtsps", "rtmp", "webrtc", "srt", } { t.Run(ca, func(t *testing.T) { conf := "api: yes\n" if ca == "rtsps" { conf += "protocols: [tcp]\n" + "encryption: strict\n" + "serverCert: " + serverCertFpath + "\n" + "serverKey: " + serverKeyFpath + "\n" } conf += "paths:\n" + " all_others:\n" p, ok := newInstance(conf) require.Equal(t, true, ok) defer p.Close() tr := &http.Transport{} defer tr.CloseIdleConnections() hc := &http.Client{Transport: tr} medi := test.MediaH264 switch ca { case "rtsp": source := gortsplib.Client{} err := source.StartRecording("rtsp://localhost:8554/mypath", &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() case "rtsps": source := gortsplib.Client{ TLSConfig: &tls.Config{InsecureSkipVerify: true}, } err := source.StartRecording("rtsps://localhost:8322/mypath", &description.Session{Medias: []*description.Media{medi}}) require.NoError(t, err) defer source.Close() case "rtmp": u, err := url.Parse("rtmp://localhost:1935/mypath") require.NoError(t, err) nconn, err := net.Dial("tcp", u.Host) require.NoError(t, err) defer nconn.Close() conn, err := rtmp.NewClientConn(nconn, u, true) require.NoError(t, err) _, err = rtmp.NewWriter(conn, test.FormatH264, nil) require.NoError(t, err) case "webrtc": u, err := url.Parse("http://localhost:8889/mypath/whip") require.NoError(t, err) c := &webrtc.WHIPClient{ HTTPClient: hc, URL: u, Log: test.NilLogger, } _, err = c.Publish(context.Background(), medi.Formats[0], nil) require.NoError(t, err) defer func() { require.Error(t, c.Close()) }() case "srt": conf := srt.DefaultConfig() conf.StreamId = "publish:mypath" conn, err := srt.Dial("srt", "localhost:8890", conf) require.NoError(t, err) defer conn.Close() track := &mpegts.Track{ Codec: &mpegts.CodecH264{}, } bw := bufio.NewWriter(conn) w := mpegts.NewWriter(bw, []*mpegts.Track{track}) require.NoError(t, err) err = w.WriteH26x(track, 0, 0, true, [][]byte{{1}}) require.NoError(t, err) err = bw.Flush() require.NoError(t, err) } var pa string switch ca { case "rtsp": pa = "rtspsessions" case "rtsps": pa = "rtspssessions" case "rtmp": pa = "rtmpconns" case "webrtc": pa = "webrtcsessions" case "srt": pa = "srtconns" } var out1 struct { Items []struct { ID string `json:"id"` } `json:"items"` } httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/list", nil, &out1) httpRequest(t, hc, http.MethodPost, "http://localhost:9997/v3/"+pa+"/kick/"+out1.Items[0].ID, nil, nil) var out2 struct { Items []struct { ID string `json:"id"` } `json:"items"` } httpRequest(t, hc, http.MethodGet, "http://localhost:9997/v3/"+pa+"/list", nil, &out2) require.Equal(t, 0, len(out2.Items)) }) } } func TestAPIProtocolKickNotFound(t *testing.T) { serverCertFpath, err := test.CreateTempFile(test.TLSCertPub) require.NoError(t, err) defer os.Remove(serverCertFpath) serverKeyFpath, err := test.CreateTempFile(test.TLSCertKey) require.NoError(t, err) defer os.Remove(serverKeyFpath) for _, ca := range []string{ "rtsp", "rtsps", "rtmp", "webrtc", "srt", } { t.Run(ca, func(t *testing.T) { conf := "api: yes\n" if ca == "rtsps" { conf += "protocols: [tcp]\n" + "encryption: strict\n" + "serverCert: " + serverCertFpath + "\n" + "serverKey: " + serverKeyFpath + "\n" } conf += "paths:\n" + " all_others:\n" p, ok := newInstance(conf) require.Equal(t, true, ok) defer p.Close() tr := &http.Transport{} defer tr.CloseIdleConnections() hc := &http.Client{Transport: tr} var pa string switch ca { case "rtsp": pa = "rtspsessions" case "rtsps": pa = "rtspssessions" case "rtmp": pa = "rtmpconns" case "webrtc": pa = "webrtcsessions" case "srt": pa = "srtconns" } func() { req, err := http.NewRequest(http.MethodPost, "http://localhost:9997/v3/"+pa+"/kick/"+uuid.New().String(), nil) require.NoError(t, err) res, err := hc.Do(req) require.NoError(t, err) defer res.Body.Close() require.Equal(t, http.StatusNotFound, res.StatusCode) switch ca { case "rtsp conns", "rtsps conns", "rtmp", "rtmps", "srt": checkError(t, "connection not found", res.Body) case "rtsp sessions", "rtsps sessions", "webrtc": checkError(t, "session not found", res.Body) case "hls": checkError(t, "muxer not found", res.Body) } }() }) } }